Могу ли я установить " isa " атрибута объекта Loose при построении?


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

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::Adult и т.д. при создании моего объекта. Возможно ли это, или я должен создать различные объекты, которые будут идентичны, за исключением isa атрибута people?

(это напоминает мнеJava generics ).

2 3

2 ответа:

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

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, что, как правило, довольно большой кодовый запах.

В качестве альтернативы можно просто написать подтип который принимает либо список либо 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',
    ...,
);
Я бы, наверное, выбрал первое решение, чтобы иметь возможность решать на основе класса объектов, содержит ли он детей, взрослых или что-то еще.

Если Вам нравится 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' можно добавлять только классы, реализующие интерфейсы::Person interface.