Hack 50. Autogenerate Your Accessors


Stop writing accessor methods by hand.

One of the Perl virtues is laziness. This doesn't mean not doing your work, it means doing your work with as little effort as possible. When you find yourself typing the same code over and over again, stop! Make the computer do the work.

Method accessors/mutators (getters/setters) are a case in point. Here's a simple object-oriented module:

package My::Customer; use strict; use warnings; sub new { bless { }, shift } sub first_name {     my $self            = shift;     return $self->{first_name} unless @_;     $self->{first_name} = shift;     return $self; } sub last_name {     my $self           = shift;     return $self->{last_name} unless @_;     $self->{last_name} = shift;     return $self; } sub full_name {     my $self = shift;     return join ' ', $self->first_name( ), $self->last_name( ); } 1;

and a small program to use it:

my $cust = My::Customer->new( ); $cust->first_name( 'John' ); $cust->last_name( 'Public' ); print $cust->full_name( );

That prints John Public.

Of course, if this is really is a customer object, it needs to do more. You might need to set a customer's credit rating, the identity of a primary salesperson, and so on.

As you can see, the first_name and last_name methods are effectively duplicates of one another. New accessors are likely to be the very similar. Can you automate this?

The Hack

There are many modules on the CPAN which handle this, all in slightly different flavors. Here are twoone of the most widespread and one of the least constraining.

Class::MethodMaker

One of the oldest such module is Class::MethodMaker, originally released in 1996. It is very feature rich, and although the documentation can seem a bit daunting, the module itself is very easy to use. To convert the My::Customer code, write:

package My::Customer; use strict; use warnings; use Class::MethodMaker[                   new    => [qw( new )],                   scalar => [qw( first_name last_name )],]; sub full_name {     my $self = shift;     return join ' ', $self->first_name( ), $self->last_name( ); }

The constructor is very straightforward, but what's up with first_name and last_name? The arguments passed to Class::MethodMaker cause it to create two getter/setters which contain scalar values. However, even though this code appears to behave identically, it's actually much more powerful.

Do you want to check that no one ever set an object's first_name, as opposed to having set it to an undefined value?

print $cust->first_name_isset( ) ? 'true' : 'false';

Even if you set first_name to undef, first_name_isset( ) will return true. Of course, sometimes you will want to be unset, even after you've set it. That works, too:

$cust->first_name( 'Ozymandias' ); print $cust->first_name_isset( ) ? 'true' : 'false'; # true $cust->first_name_reset( ); print $cust->first_name_isset( ) ? 'true' : 'false'; # false

Class::BuildMethods

Class::MethodMaker also has built-in support for arrays, hashes, and many other useful features. However, it requires you use a blessed hash for your objects. In fact, most of the accessor builder modules on the CPAN make assumptions about your object's internals. One exception to this is Class::BuildMethods.

Class::BuildMethods allows you to build accessors for your class regardless of whether it's a blessed hash, arrayref, regular expression, or whatever. It does this by borrowing a trick from inside out objects [Hack #43]. Typical code looks like:

package My::Customer; use strict; use warnings; use Class::BuildMethods qw(                     first_name                     last_name                   ); # Note that you can use an array reference, if you prefer sub new { bless [ ], shift } sub full_name {     my $self = shift;     return join ' ', $self->first_name( ), $self->last_name( ); } 1;

Use this class just like any other. Internally it indexes the accessor values by the address of the object. It handles object destruction for you by default, but allows you to handle this manually if you need special behavior on DESTROY (such as releasing locks).

Class::BuildMethods is very simple, by design. Like most other accessor generators, it provides some convenience features, but only in the form of default values and data validation:

use Class::BuildMethods   'name',   gender => { default  => 'male' },   age    => { validate => sub   {       my ($self, $age) = @_;       carp 'You can't enlist if you're a minor'           if ( $age < 18 && ! $self->is_emancipated( ) );   }};

With this code, gender( ) will return male unless you set it to some other value. age( ) shows how to provide flexible validation. Because the validate( ) method points to a subroutine reference rather than providing special validation handlers, the author's assumptions of how you should validate your code don't constrain you.

Class::BuildMethods always assumes that a setter takes a single value, so you must pass references for arrays and hashes. It also does not provide class methods (a.k.a. static methods). These limitations may mean this code doesn't fit your needs, but this module was designed to be simple. You can read and understand the docs in one sitting.

Running the Hack

Accessor generation, when it fits your needs, can remove a tremendous amount of grunt work. This is hacking your brain. As a Perl programmer, true laziness means that you waste less time on the fiddly bits so you can spend more time worrying about the hard stuff.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net