Inside-Out Objects
Randal L. Schwartz
In my previous article, "Generating Object Accessors", which appeared in the
January 2006 issue, I created a traditional hash-based Perl object: a Rectangle
with two attributes (width and height) using the constructor and accessors like
so:
package Rectangle;
sub new {
my $class = shift;
my %args = @_;
my $self = {
width => $args{width} || 0;
height => $args{height} || 0;
};
return bless $self, $class;
}
sub width {
my $self = shift;
return $self->{width};
}
sub set_width {
my $self = shift;
$self->{width} = shift;
}
sub height {
my $self = shift;
return $self->{height};
}
sub set_height {
my $self = shift;
$self->{height} = shift;
}
I can construct a 3-by-4 rectangle easily:
my $r = Rectangle->new(width => 3, height => 4);
At this point, $r is an object of type Rectangle, but it's also simply a hashref.
For example, the code in set_width merely deferences a value like $r to
gain access to the hash element with a key of width. But does Perl require
such code to be located within the Rectangle package? No. As a user of
the Rectangle class, I could easily say:
$r->{width} = 5;
and update the width from 3 to 5. This is "peering inside the box" and will lead
to fragile code, because we've now exposed the implementation of the object, not
just the interface.
For example, suppose we modify the set_width method to ensure that
the width is never negative:
use Carp qw(croak);
sub set_width {
my $self = shift;
my $width = shift;
croak "$self: width cannot be negative: $width"
if $width < 0;
$self->{width} = $width;
}
If the $width is less than 0, we croak, triggering a fatal exception, but
blaming the caller of this method.
|