11.5 Creating Getters and Setters More Easily
If all that coding for creating
accessors using AUTOLOAD looks messy, rest assured
that you really don't need to tackle it, because
there's a CPAN module that does it a bit more
directly: Class::MethodMaker.
For example, a simplified version of the Animal
class might be defined as follows:
package Animal;
use Class::MethodMaker
new_with_init => 'new',
get_set => [-eiffel => [qw(color height name age)]],
abstract => [qw(sound)],
;
sub init {
my $self = shift;
$self->set_color($self->default_color);
}
sub named {
my $self = shift->new;
$self->set_name(shift);
$self;
}
sub speak {
my $self = shift;
print $self->name, " goes ", $self->sound, "\n";
}
sub eat {
my $self = shift;
my $food = shift;
print $self->name, " eats $food\n";
}
sub default_color {
"brown";
}
The getters and setters for the four instance attributes (name,
height, color, and age) are defined automatically, using the method
color to get the color and
set_color to set the color. (The
eiffel flag says "do it the way
the Eiffel language does it," which is the way it
should be done here.) The messy blessing step is now hidden behind a
simple new method. The initial color is defined as
the default color, as before, because the init
method is automatically called from new.
However, you can still call Horse->named('Mr.
Ed') because it immediately calls the
new routine as well.
The sound method is
automatically generated as an abstract method.
Abstract methods are placeholders, meant to be
defined in a subclass. If a subclass fails to define the method, the
method generated for Animal's
sound dies.
You lose the ability to call the getters
(such as name) on the class itself, rather than an
instance. In turn, this breaks your prior usage of calling
speak and eat on generic
animals, since they call the accessors. One way around this is to
define a more general version of name to handle
either a class or instance and then change the other routines to call
it:
sub generic_name {
my $either = shift;
ref $either ? $either->name : "an unnamed $either";
}
sub speak {
my $either = shift;
print $either->generic_name, " goes ", $either->sound, "\n";
}
sub eat {
my $either = shift;
my $food = shift;
print $either->generic_name, " eats $food\n";
}
There. Now
it's looking nearly drop-in compatible with the
previous definition, except for those friend classes that referenced
the attribute names directly in the hash as the initial-cap-keyed
versions (such as Color) rather than through the
accessors ($self->color).
That brings up the maintenance issue again. The more you can decouple
your implementation (hash versus array, names of hash keys, or types
of elements) from the interface (method names, parameter lists, or
types of return values), the more flexible and maintainable your
system becomes.
That
flexibility is not free, however. The cost of a method call is higher
than the cost of a hash lookup, so it may be acceptable (or even
necessary) for a friend class to peer inside. You may have to pay the
programmer-time price of development and maintenance so you
don't pay the runtime price of an overly flexible
system.
On the other hand, don't go overboard in the other
direction. Many anecdotes float around about systems where everything
was so indirected (to be flexible) that the system ran too slowly to
be used.
|