Могу ли я установить 'isa' атрибута объекта Moose при создании? - PullRequest
3 голосов
/ 12 ноября 2010

У меня есть объект Moose со следующим атрибутом:

has 'people' => (
 is      => 'ro',
 isa     => 'ArrayRef[Person::Child]',
 traits  => ['Array'],
 default => sub { [] },
 handles => {
  all_people     => 'elements',
  get_people     => 'get',
  push_people    => 'push',
  pop_people     => 'pop',
  count_people   => 'count',
  sort_people    => 'sort',
  grep_people    => 'grep',
 },
);

Обратите внимание, что isa установлено как 'ArrayRef [Person :: Child]'.

Я хотел бы иметь возможность выбирать между Person::Child, Person::Adult и т. Д. При создании моего объекта. Возможно ли это, или я должен создать разные объекты, которые будут идентичны, кроме isa атрибута people?

(Это напоминает мне обобщения Java ).

Ответы [ 2 ]

5 голосов
/ 12 ноября 2010

Почему бы не переместить определение этого атрибута в роль и повторно использовать его с соответствующей параметризацией в других классах?

package MyApp::Thingy::HasPeople;

use MooseX::Role::Parameterized;

parameter person_type => (
    isa      => 'Str',
    required => 1,
);

role {
    my $person_type = shift->person_type;

    has 'people' => (
        is      => 'ro',
        isa     => "ArrayRef[${person_type}]",
        traits  => ['Array'],
        default => sub { [] },
        handles => {
            all_people   => 'elements',
            get_people   => 'get',
            push_people  => 'push',
            pop_people   => 'pop',
            count_people => 'count',
            sort_people  => 'sort',
            grep_people  => 'grep',
        },
    );
};

1;

и где-то еще в классах, которым этот атрибут действительно необходим

package MyApp::Thingy::WithChildren;
use Moose;

with 'MyApp::Thingy::HasPeople' => { person_type => 'Person::Child' };

1;

или

package MyApp::Thingy::WithAdults;
use Moose;

with 'MyApp::Thingy::HasPeople' => { person_type => 'Person::Adult' };

1;

Таким образом, вы получаете возможность не поддерживать атрибут в двух местах и ​​не получите объекты одного класса, но разные API, что имеет тенденцию кбыть довольно большим запахом кода.

В качестве альтернативы, вы можете просто написать подтип ArrayRef, который принимает либо список Person::Child или Person::Adult, либо любые другие типы людей, которые у вас есть, но толькодо тех пор, пока все элементы этого списка имеют одинаковый вид.

use List::AllUtils 'all';
subtype 'PersonList', as 'ArrayRef', where {
    my $class = blessed $_->[0];
    $_->[0]->isa('Person') && all { blessed $_ eq $class } @{ $_ };
};

has persons => (
    is  => 'ro',
    isa => 'PersonList',
    ...,
);

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

1 голос
/ 06 декабря 2010

Если вам нравится Java, вам может понравиться это:

package Interfaces::Person;

use Moose::Role;

requires qw( list all attributes or methods that you require );

1;

Подтвердите, что Person :: Adult и Person :: Child реализуют этот интерфейс:

package Person::Adult;

...
# add at the end
with qw(Interfaces::Person);

1;

и

package Person::Child;

...
# add at the end
with qw(Interfaces::Person);

1;

И вернемся в основной класс:

package My::People;
use Moose;
use MooseX::Types::Moose qw( ArrayRef );
use MooseX::Types::Implements qw( Implements );

has 'people' => (
 is      => 'ro',
 isa     => ArrayRef[Implements[qw(Interfaces::Person)]],
 traits  => ['Array'],
 default => sub { [] },
 handles => {
  all_people     => 'elements',
  get_people     => 'get',
  push_people    => 'push',
  pop_people     => 'pop',
  count_people   => 'count',
  sort_people    => 'sort',
  grep_people    => 'grep',
 },
);

И теперь к классу people могут быть добавлены только классы, которые реализуют интерфейс Interfaces :: Person.

...