09 March 2008

Class::Accessor constructors

I'm a pretty big fan of Class::Accessor. It's great for those occasions when you need to write a Perl module which has lots of attributes. You tell your module to inherit from Class::Accessor, provide a list of attributes, and your module automatically has accessors and mutators for all of those attributes. Class::Accessor even takes care of creating your module's constructor.

That last point was actually giving me some trouble the other day. I was writing a Perl module, and it turned out to have several attributes (counters that needed to be incremented while parsing a file), so I decided to have my module inherit from Class::Accessor. But one of the attributes was going to be an instance of another class (I wanted to use composition, rather than multiple inheritance), and I wanted to instantiate this object when my object is instantiated. But since Class::Accessor creates my constructor automatically, it wasn't clear to me how I'd do this.

With a little fiddling, I was able to override the Class::Accessor constructor in such a way that it still created my accessors and mutators, but also allowed me to do other object initialization tasks:

package Gakkk;

use strict;
use diagnostics;
use warnings;

use base qw/ Class::Accessor /;
Gakkk->mk_accessors(
qw/
flamningle
line_number
num_parse_errors
num_zortbiptons
/
);

use Some::Other::Class;

sub new {
my $class = shift @_;

my $self = $class->SUPER::new(@_);
$self->flamningle( Some::Other::Class->new() );
$self->line_number(0);
$self->num_parse_errors(0);
$self->num_zortbiptons(0);

return $self;
}

# other methods, ...

1;


The call to $class->SUPER::new(@_) gives what's left of the argument list (@_) to the constructor of the parent class (Class::Accessor) and returns an instance of my class. I'm then able to initialize my object attributes without requiring that the calling code do it explicitly. Without overriding the constructor, the caller would have to do something like this:

my $gakkk = Gakkk->new(
{
flamningle => Some::Other::Class->new(),
line_number => 0,
num_parse_errors => 0,
num_zortbiptons => 0,
}
);


Having overridden the constructor, the caller can instantiate the class like this:

my $gakkk = Gakkk->new();

2 comments:

Daniel said...

If you were to now instantiate a Gakk with

my $gakkk = Gakkk->new({line_number => 1});

... I suspect your overridden constructor will still end up setting line_number to 0, since it explicitly sets it to 0 after calling the superclass constructor with the user-supplied value?

mbrisby said...

Thanks for writing, Daniel.

You're absolutely right that the way I wrote this prevents the user from setting those attributes to arbitrary values at instantiation time.

In this case, those values (in Gakkk's constructor) are the values I needed for what I was doing, so this saves me the trouble of initializing those attributes myself.

An alternative would be to change Gakkk's constructor to check each attribute: leave the attribute alone if the attribute is defined (as would be the case if the user provided a value in the constructor call), otherwise initialize it to some default value.