Синтаксис объектно-ориентированного конструктора Perl и именованные параметры - PullRequest
27 голосов
/ 09 ноября 2009

Я немного озадачен тем, что происходит в конструкторах Perl. Я нашел эти два примера perldoc perlbot .

package Foo;

#In Perl, the constructor is just a subroutine called new.
sub new {
  #I don't get what this line does at all, but I always see it. Do I need it?
  my $type = shift;

  #I'm turning the array of inputs into a hash, called parameters.
  my %params = @_;

  #I'm making a new hash called $self to store my instance variables?
  my $self = {};

  #I'm adding two values to the instance variables called "High" and "Low".
  #But I'm not sure how $params{'High'} has any meaning, since it was an
  #array, and I turned it into a hash.
  $self->{'High'} = $params{'High'};
  $self->{'Low'} = $params{'Low'};

  #Even though I read the page on [bless][2], I still don't get what it does.
  bless $self, $type;
}

И еще один пример:

package Bar;

sub new {
  my $type = shift;

  #I still don't see how I can just turn an array into a hash and expect things
  #to work out for me.
  my %params = @_;
  my $self = [];

  #Exactly where did params{'Left'} and params{'Right'} come from?
  $self->[0] = $params{'Left'};
  $self->[1] = $params{'Right'};

  #and again with the bless.
  bless $self, $type;
}

А вот скрипт, который использует эти объекты:

package main;

$a = Foo->new( 'High' => 42, 'Low' => 11 );
print "High=$a->{'High'}\n";
print "Low=$a->{'Low'}\n";

$b = Bar->new( 'Left' => 78, 'Right' => 40 );
print "Left=$b->[0]\n";
print "Right=$b->[1]\n";

Я ввел вопросы / путаницу, которые у меня были в коде, в качестве комментариев.

Ответы [ 7 ]

53 голосов
/ 09 ноября 2009

Чтобы ответить на основной вопрос вашего вопроса, поскольку хеш можно инициализировать как список пар key => value, вы можете отправить такой список функции, а затем присвоить @_ хешу. Это стандартный способ выполнения именованных параметров в Perl.

Например,

sub foo { 
    my %stuff = @_;
    ...
}

foo( beer => 'good', vodka => 'great' );

В результате %stuff в подпрограмме foo будет иметь хеш с двумя ключами beer и vodka и соответствующими значениями.

Теперь в OO Perl есть несколько дополнительных морщин. Всякий раз, когда вы используете оператор стрелки (->) для вызова метода, все, что было с левой стороны стрелки, прикрепляется к началу массива @_.

Так что, если вы скажете Foo->new( 1, 2, 3 );

Тогда внутри вашего конструктора, @_ будет выглядеть так: ( 'Foo', 1, 2, 3 ).

Таким образом, мы используем shift, который без аргументов действует неявно на @_, чтобы получить этот первый элемент из @_ и присвоить его $type. После этого у @_ остались только наши пары имя / значение, и мы можем для удобства присвоить его непосредственно хешу.

Затем мы используем это $type значение для bless. Все, что делает bless, это берет ссылку (в вашем первом примере хэш-ссылку) и говорит: «эта ссылка связана с конкретным пакетом». Алаказзам, у тебя есть объект.

Помните, что $type содержит строку 'Foo', которая является названием нашего пакета. Если вы не задаете второй аргумент для bless, он будет использовать имя текущего пакета, который также будет работать в этом примере, но не будет работать для унаследованных конструкторов ,

18 голосов
/ 09 ноября 2009

0,1. В Perl конструктор - это просто подпрограмма с именем new.

Да, по соглашению new это конструктор. Может также выполнить инициализацию или нет. new должен вернуть объект в случае успеха или выдать исключение (die / croak), если произошла ошибка, препятствующая созданию объекта.

Вы можете называть свой конструктор как угодно, иметь столько конструкторов, сколько хотите, и даже встраивать благословенные объекты в любое пространство имен, которое пожелаете (не то, чтобы это была хорошая идея).

0,2. Я совсем не понимаю, что делает my $type = shift;, но я всегда это вижу. Мне это нужно?

shift без аргументов снимает аргумент с заголовка @_ и присваивает его $type. Оператор -> передает инвокант (левая сторона) в качестве первого аргумента подпрограмме. Таким образом, эта строка получает имя класса из списка аргументов. И да, тебе это нужно.

0,3. Как массив входных данных становится хешем %params? my %params = @_;

Назначение в хеш выполняется в контексте списка, при этом пары элементов списка группируются в пары ключ / значение. Так что %foo = 1, 2, 3, 4; создает хеш, такой что $foo{1} == 2 и $foo{3} == 4. Обычно это делается для создания именованных параметров для подпрограммы. Если подпрограмме передается нечетное число аргументов, то предупреждение будет сгенерировано, если предупреждения включены.

0,4. Что делает 'my $ self = {}; `do?

Эта строка создает анонимную ссылку на хеш и присваивает ее лексической переменной $self. Ссылка на хеш будет хранить данные для объекта. Как правило, ключи в хэше имеют однозначное соответствие атрибутам объекта. Поэтому, если у класса Foo есть атрибуты 'size' и 'color', если вы проверите содержимое объекта Foo, вы увидите что-то вроде $foo = { size => 'm', color => 'black' };.

0,5. Учитывая $self->{'High'} = $params{'High'}; откуда $params{'High'} откуда?

Этот код опирается на аргументы, переданные new. Если new был назван как Foo->new( High => 46 ), то хэш, созданный в соответствии с вопросом 3, будет иметь значение для ключа High (46). В этом случае это эквивалентно высказыванию $self->{High} = 46. Но если метод вызывается как Foo->new(), тогда никакое значение не будет доступно, и у нас будет $self->{High} = undef.

0,6. Что bless делает?

bless берет ссылку и связывается с конкретным пакетом, так что вы можете использовать его для вызова методов. С одним аргументом ссылка ассоциируется с текущим пакетом. С двумя аргументами второй аргумент указывает пакет, с которым связана ссылка. Лучше всегда использовать форму с двумя аргументами, чтобы ваши конструкторы могли наследоваться подклассом и при этом функционировать должным образом.

Наконец, я перепишу ваш метод доступа к объектам, основанный на хэше, как я написал бы, используя классический OO Perl.

package Foo;

use strict;
use warnings;
use Carp qw(croak);

sub new {
    my $class = shift;

    croak "Illegal parameter list has odd number of values" 
        if @_ % 2;

    my %params = @_;

    my $self = {};
    bless $self, $class;

    # This could be abstracted out into a method call if you 
    # expect to need to override this check.
    for my $required (qw{ name rank serial_number  });
        croak "Required parameter '$required' not passed to '$class' constructor"
            unless exists $params{$required};  
    }

    # initialize all attributes by passing arguments to accessor methods.
    for my $attrib ( keys %params ) {

        croak "Invalid parameter '$attrib' passed to '$class' constructor"
            unless $self->can( $attrib );

        $self->$attrib( $params{$attrib} );
    }

    return $self;
}
9 голосов
/ 09 ноября 2009

Некоторые пункты, которые еще не были рассмотрены:

В Perl конструктор - это просто подпрограмма называется new.

Не совсем. Вызов конструктора new - это просто соглашение. Вы можете называть это как угодно. С точки зрения Perl в этом имени нет ничего особенного.

bless $self, $type;

Оба ваших примера не возвращают результат благословения явно. Я надеюсь, что вы знаете, что они все равно делают это безоговорочно.

9 голосов
/ 09 ноября 2009

Ваш вопрос не об OO Perl. Вы не понимаете структуры данных.

Хеш может быть инициализирован с использованием списка или массива:

my @x = ('High' => 42, 'Low' => 11);
my %h = @x;

use Data::Dumper;
print Dumper \%h;
$VAR1 = {
          'Low' => 11,
          'High' => 42
        };

Когда вы вызываете метод для bless ed ссылки, ссылка добавляется к списку аргументов, который получает метод:

#!/usr/bin/perl

package My::Mod;

use strict;
use warnings;

use Data::Dumper;
$Data::Dumper::Indent = 0;

sub new { bless [] => shift }

sub frobnicate { Dumper(\@_) }

package main;

use strict;
use warnings;

my $x = My::Mod->new;

# invoke instance method
print $x->frobnicate('High' => 42, 'Low' => 11);

# invoke class method
print My::Mod->frobnicate('High' => 42, 'Low' => 11);

# call sub frobnicate in package My::Mod
print My::Mod::frobnicate('High' => 42, 'Low' => 11);

Выход:

$VAR1 = [bless( [], 'My::Mod' ),'High',42,'Low',11];
$VAR1 = ['My::Mod','High',42,'Low',11];
$VAR1 = ['High',42,'Low',11];
8 голосов
/ 09 ноября 2009

Если вы присваиваете массив хешу, perl рассматривает чередующиеся элементы в массиве как ключи и значения. Ваш массив выглядит как

my @array = (key1, val1, key2, val2, key3, val3, ...);

Когда вы присваиваете% хешу, вы получаете

my %hash = @array;
# %hash = ( key1 => val1, key2 => val2, key3 => val3, ...);

Это еще один способ сказать, что в синтаксисе построения списка / хэша perl "," и "=>" означают одно и то же.

5 голосов
/ 09 ноября 2009

В Perl все аргументы подпрограмм передаются через предопределенный массив @_.

shift удаляет и возвращает первый элемент из массива @_. В Perl OO это инвокант метода - обычно это имя класса для конструкторов и объект для других методов.

Хэши сглаживаются и могут быть инициализированы списками. Это обычная уловка для эмуляции именованных аргументов в подпрограммах. например, * +1008 *

Employee->new(name => 'Fred Flintstone', occupation => 'quarry worker');

Игнорируя имя класса (которое смещено), нечетные элементы становятся хеш-ключами, а четные элементы становятся соответствующими значениями.

my $self = {} создает новый хэш ссылка для хранения данных экземпляра. Функция bless превращает обычную ссылку на хеш $self в объект. Все, что он делает - это добавляет метаданные, которые идентифицируют ссылку как принадлежащую классу.

1 голос
/ 21 сентября 2011

Да, я знаю, что я немного некромант, но ...

Хотя все эти ответы превосходны, я подумал, что упомяну Лось . Moose упрощает работу с конструкторами (package Foo;use Moose; автоматически предоставляет конструктор с именем new (хотя имя «new» можно переопределить, если хотите)), но не отнимает никаких настроек , если вам нужно это.

После того, как я просмотрел документацию по Moose (которая в целом довольно хороша, и есть намного больше обучающих фрагментов, если вы правильно настроили Google), я никогда не оглядывался назад.

...