I have a Moose object with the following attribute:
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',
},
);
Note the isa
is set as 'ArrayRef[Person::Child]'.
I would like to be able to choose between Person::Child
, Person::Adult
etc. upon creation of my object. Is that possible or must I create different objects that will be identical except the isa
of the people
attribute?
(This reminds me of Java generics).
Why not move the definition of that attribute into a role and reuse it, with the appropriate parameterisation, in other classes?
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;
and somewhere else, in the classes that actually need that attribute
package MyApp::Thingy::WithChildren;
use Moose;
with 'MyApp::Thingy::HasPeople' => { person_type => 'Person::Child' };
1;
or
package MyApp::Thingy::WithAdults;
use Moose;
with 'MyApp::Thingy::HasPeople' => { person_type => 'Person::Adult' };
1;
That way you get to both not maintain the attribute in two places, and won't end up with objects of the same class but different APIs, which tends to be a pretty big code smell.
Alternatively, you could simply write a subtype of ArrayRef
that accepts either a list of either Person::Child
or Person::Adult
or whatever other kinds of persons you have, but only as long as all elements of that list are of the same kind.
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',
...,
);
I'd probably go for the first solution in order to be able to decide based on an objects class if it contains children, adults, or whatever.