Firstly, I will start with a few comments, then I will answer the meat of your question.
OO Perl is slightly different from other OO systems. There is a very thin layer of basic OO support that allows you to make your objects almost whatever you want. From the bottom, you can make your objects almost whatever you want. The classic OO Perl includes many code templates, as you implement accessors and mutators for each attribute, maybe add type checking, etc. This has led to the emergence of a wide range of tools for automating the production of templates.
There are three ways that I approach OO Perl: Moose, a classic hash based on all manual encodings, and Class :: Struct. Moose is great for systems where you have complex needs, but it greatly affects the launch time of the application. If startup time is important for your application, Moose is out of the question right now. The :: Struct class is a great way to get the lowest common denominator, a quick, simple OO application together, at the bottom it does not support inheritance. In this case, a manually encoded OOP is entered. If Moose or Class :: Struct are not viable options for one reason or another, I will return to the basics. This strategy worked well for me. The only change I have felt in the last few years is to add Moose to my standard toolkit. This is a nice addition.
Damian Conway Object Oriented Perl is an amazing book that clearly describes OOP, how OO Perl works and how to create objects that can do amazing things. It's a bit outdated, but the book is still holding on. Any serious OO Perl student should read this book.
Now, for your question -
It seems to me that breed2 not an attribute of your object, but a method.
use Class::Struct; use strict; use warnings; struct Breed => { name => '$', cross => '$', }; struct Cat => [ name => '$', kittens => '@', markings => '%', breed => 'Breed', ]; my $cat = Cat->new( name => 'Socks', kittens => ['Monica', 'Kenneth'], markings => { socks=>1, blaze=>"white" }, breed => { name=>'short-hair', cross=>1 }, );
Things get a little more hairy if you want to keep a set of predefined breeds and breed2 select a breed object by name if not set.
This abbreviated Cat implementation uses class data to track breeds of cats and
package Cat; use strict; use warnings; use Carp qw( croak ); my %breeds = map { $_->{name}, Breed->new( %$_ ) } ( { name=>'short-hair', cross=>1 }, { name=>'long-hair', cross=>1 }, { name=>'siamese', cross=>0 }, ); sub new { my $class = shift; my %args = @_; my $self = {}; bless $self, $class; for my $arg ( keys %args ) { $self->$arg( $args{$arg} ) if $self->can($arg); } return $self; } sub breed { my $self = shift; if( @_ ) { my $v = shift; croak "Illegal cat breed" unless eval {$v->isa( 'Breed' ) }; $self->{breed} = $v; } return $self->{breed}; } sub breed2 { my $self = shift; my @breed_args; if( @_ ) { my $v = shift; croak "$v is not a supported breed\n" unless exists $breeds{$v}; @breed_args = ( $breeds{$v} ); } my $breed = $self->breed(@breed_args); return unless $breed; return $breed->name; }
Now let's look at the Moose solution, which uses all sorts of advanced goodies, such as coercion and type overloading:
BEGIN { package Breed; use Moose; has 'name' => ( isa => 'Str', is => 'ro', required => 1 ); has 'cross' => ( isa => 'Bool', is => 'ro', required => 1 ); use overload '""' => \&_overload_string; sub _overload_string { my $self = shift; return $self->name; } __PACKAGE__->meta->make_immutable; no Moose; 1; } BEGIN { package Cat; use Moose; use Moose::Util::TypeConstraints; use Carp; subtype 'MyTypes::CatBreed' => as class_type('Breed'); coerce 'MyTypes::CatBreed' => from 'Str' => via { Cat->supported_breed_by_name( $_ ) }; has 'name' => ( isa => 'Str', is => 'rw', required => 1 ); has 'kittens' => ( traits => ['Array'], is => 'ro', isa => 'ArrayRef[Str]', default => sub{ [] }, handles => { all_kittens => 'elements', add_kittens => 'push', get_kitten => 'get', count_kittens => 'count', has_kittens => 'count', }, ); has 'markings' => ( traits => ['Hash'], is => 'ro', isa => 'HashRef[Str]', default => sub{ {} }, handles => { set_marking => 'set', get_marking => 'get', has_marking => 'exists', all_markings => 'keys', delete_marking => 'delete', }, ); has 'breed' => ( isa => 'MyTypes::CatBreed', is => 'ro', coerce => 1, ); my %breeds; sub supported_breed_by_name { my $class = shift; my $name = shift; croak 'No breed name specified' unless defined $name and length $name; return $breeds{$name}; } sub add_breed { my $class = shift; my $breed = shift; croak 'No breed specified' unless eval { $breed->isa('Breed') }; croak 'Breed already exists' if exists $breeds{$breed}; $breeds{$breed} = $breed; return $class; } sub delete_breed { my $class = shift; my $name = shift; croak 'No breed name specified' unless defined $name and length $name; return delete $breeds{$name}; } __PACKAGE__->meta->make_immutable; no Moose; 1; }