I would suggest that the answer to #3 is:
Whether or not you've outgrown Class::Struct, in 2013 there is very little reason to use it. If you choose to use an OO helper (rather than writing OO by hand), and you want to stay light-weight and fast, use Moo. It's true that it is not core. But it is fast, minimal, and actively developed and used. And it's largely compatible with Moose classes.
With Moo, your example code above would look like this:
package Cat;
use Moo;
use MooX::Types::MooseLike::Base qw(:all);
has name => ( is => 'ro', isa => Str );
1;
package Litter;
use Moo;
use MooX::Types::MooseLike::Base qw(:all);
has cats => ( is => 'ro', isa => ArrayRef );
1;
package main;
my $cat1 = Cat->new(name=>'Garfield');
my $cat2 = Cat->new(name=>'Felix');
my $litter = Litter->new(cats => [$cat1, $cat2]);
for (@{$litter->cats}) {
print $_->name . "\n"
}
...but in this example Moo is doing more: It's assuring that the accessors "name" and "cats" can't be modified after the object has been instantiated. It's also assuring that "name" is a string.
But one real advantage is that instead of isa => ArrayRef, you could say:
isa => sub {
my $param = shift;
die "<<$param>> must be an array ref" unless ref $param eq 'ARRAY';
foreach my $element ( @{$param} ) {
die "<<$element>> isn't a Cat object"
unless ref $element eq 'Cat';
}
}
Now your object instantiation verifies that all of the elements passed by Litter->new( cats => [ ... ] ) are Cats objects. You could even get more "general" by skipping the "ref $element eq 'Cat'" test, and instead allowing any sufficiently Cat-like object to pass:
die "<<$element>> must have a name attribute"
unless $element->can('name');
With Moo you could also define a BUILDARGS subroutine that silently drops any elements from cats => [...] that don't have a "name" attribute.... if you want that behavior instead.
Now, going back to the MooX::Types::MooseLike::Base example, even these constructs are possible:
isa => ArrayRef[ InstanceOf['Cat'] ]
And if you want your Litter class to accept any object that has a name attribute (in other words, a litter of bobcats would be fine too, as long as they have a name attribute), you can...
isa => ArrayRef[ HasMethods['name'] ]
Which provides clear semantics and avoids complexity. Moo makes this sort of thing pretty simple. Moose, being Moo's big uncle also has similar syntactic sugar. But Moo is so light-weight it's a great fit for small projects.
Update: So putting it all together, here's your original code with the Moo and MooX::Types::MooseLike::Base syntax:
package Cat;
use Moo;
use MooX::Types::MooseLike::Base 'Str';
has name => ( is => 'ro', isa => Str );
1;
package Litter;
use Moo;
use MooX::Types::MooseLike::Base qw( ArrayRef HasMethods );
has cats => ( is => 'ro', isa => ArrayRef [ HasMethods ['name'] ] );
1;
package main;
my @cat_names = qw( Garfield Felix );
my $litter = Litter->new(
cats => [ map { Cat->new( name => $_ ) } @cat_names ]
);
print $_->name . "\n" for @{ $litter->cats };
Updated: Removed quotes from isa => checks since Moo requires a subref, and since that's what MooX::Types::MooseLike::Base provides.
|