Perl Design Patterns, Part 3
by Phil Crow
|
Pages: 1, 2, 3
If the object is not yet defined, AUTOLOAD calls File->new passing it the
arguments stored during construction. After that, the object is defined,
so AUTOLOAD calls the requested method on the wrapped object. The beauty
of this mechanism is that the FileProxy class only knows that the
constructor is called new. It does not need to change as changes to
File.pm are made. Any errors, such as no such method, will be fatal
as usual.
To use this proxied scheme we might employ a caller like this:
#!/usr/bin/perl
use strict; use warnings;
use FileProxy;
my $file1 = FileProxy->new("art1");
my $file2 = FileProxy->new("art2");
$file1->print_file();
$file1->print_file();
$file2->print_file();
With a couple of changes we could use this for any class. Here's the new generic version:
package DelayLoad;
use strict; use warnings;
our %proxied_classes;
sub import {
shift; # discard class name
%proxied_classes = @_;
foreach my $class (keys %proxied_classes) {
require "$class.pm";
}
}
sub new {
my $class = shift;
my $self = {
type => shift,
constructor => shift,
params => \@_,
wrapped_object => undef,
};
return bless $self, $class;
}
sub AUTOLOAD {
my $self = shift;
my $command = our $AUTOLOAD;
$command =~ s/.*://;
if ($proxied_classes{$command}) {
return $self->new($command, $proxied_classes{$command}, @_);
}
else {
unless (defined $self->{wrapped_object}) {
my $proxied_class = $self->{type};
my $constructor = $self->{constructor};
$self->{wrapped_object} = $proxied_class
->$constructor(@{$self->{params}});
}
$self->{wrapped_object}->$command(@_);
}
}
1;
The first change is cosmetic: the name now reflects the nature of the
proxy. Other changes include a new method: import. Even though its name
is lower case, Perl calls it whenever the caller says use DelayLoad (see
below). It does two things. First, it stores the name of each proxied
class in the %proxied_classes package global. Second, it requires each
module. require is like use, but it happens at run time instead of
compile time. (use also imports symbols, but then your object oriented
module shouldn't be exporting anything anyway.)
The constructor now stores a bit more information. In addition to saving
room for the wrapped object and storing the params, it also records
the name of the class and of that class's constructor. These will be
used in AUTOLOAD.
The only other changes are in the AUTOLOAD method. There are two
changes. The easiest one is to look up the class and constructor
names in the DelayLoad object instead of just calling File->new.
The other change is used during construction. My explanation of it will make more sense, if you see the new caller first.
The new version requires a couple of changes to the caller. One change is on the use line which becomes:
use DelayLoad "File" => "new";
This uses DelayLoad, tells it we want to be able to delay
loads for File objects, and that File's constructor is called new.
The other change is in how we construct the delayed object:
my $file1 = DelayLoad->File("art1");
my $file2 = DelayLoad->File("art2");
This explains the unexplained piece in AUTOLOAD above. When the user
calls the File method, AUTOLOAD notices that this ``method'' is
really the name of a delay loaded class. When the if in AUTOLOAD
is true (i.e. the method is really a key in %proxied_classes), the caller
is given a new DelayLoad object primed for later use. When the if
fails, DelayLoad works like FileLoad: it constructs the object, if
needed and calls the requested method.
The fundamental point of this example is that Perl allows us to implement
proxies without knowing very much about the underlying class. In this
case, import receives the necessary information from the caller,
AUTOLOAD takes care of the rest. Making the caller work is not always
a good idea. Here it makes sense. If she knows she wants to delay loading
objects until they are really needed, she must at least know the API for
those objects. In the API is the name of the constructor, which she mentions
in the use statement so Perl can pass to DelayLoad::import for her.
Keep in mind that AUTOLOAD is not designed for this sort of work. Its
real purpose in life is to load subroutines on demand for the current package.
It can't do that here, since changing the subroutines affects all instances
of a class. Here we are AUTOLOADing data, not routines.
By suitably adjusting import and AUTOLOAD, you can make the proxy do
many other things.
Summary
In this article, I have finally shown object oriented patterns.
We saw how to implement a Factory so our callers can choose their favorite
driver, how to build composite structures and routines that
traverse them (without explicit first_child and next pointers that
would be needed in languages without quality built-in lists), and how
to stand as a proxy between a caller and a class with import and
AUTOLOAD.
Author's Note
This is the final article in this series, but look for a book, Design Patterns in Perl from Apress at your favorite bookseller in the near future.

