|
Autoloading is a way to intercept calls to undefined methods. An autoload routine may choose
to create a new function on the fly, either loaded from disk or perhaps just eval()ed right
there. This define-on-the-fly strategy is why it's called autoloading.
But that's only one possible approach. Another one is to just have the autoloaded method
itself directly provide the requested service. When used in this way, you may think of
autoloaded methods as "proxy" methods.
When Perl tries to call an undefined function in a particular package and that function is
not defined, it looks for a function in that same package called AUTOLOAD. If one exists, it's
called with the same arguments as the original function would have had. The fully-qualified name
of the function is stored in that package's global variable $AUTOLOAD. Once called, the function
can do anything it would like, including defining a new function by the right name, and then
doing a really fancy kind of goto right to it, erasing itself from the call stack.
What does this have to do with objects? After all, we keep talking about functions, not
methods. Well, since a method is just a function with an extra argument and some fancier
semantics about where it's found, we can use autoloading for methods, too. Perl doesn't start
looking for an AUTOLOAD method until it has exhausted the recursive hunt up through @ISA,
though. Some programmers have even been known to define a UNIVERSAL::AUTOLOAD method to trap
unresolved method calls to any kind of object.
You probably began to get a little suspicious about the duplicated code way back earlier when
we first showed you the Person class, and then later the Employee class. Each method used to
access the hash fields looked virtually identical. This should have tickled that great
programming virtue, Impatience, but for the time, we let Laziness win out, and so did nothing.
Proxy methods can cure this.
Instead of writing a new function every time we want a new data field, we'll use the autoload
mechanism to generate (actually, mimic) methods on the fly. To verify that we're accessing a
valid member, we will check against an _permitted (pronounced
"under-permitted") field, which is a reference to a file-scoped lexical (like a C file
static) hash of permitted fields in this record called %fields. Why the underscore? For the same
reason as the _CENSUS field we once used: as a marker that means "for internal use
only".
Here's what the module initialization code and class constructor will look like when taking
this approach:
package Person;
use Carp;
our $AUTOLOAD; # it's a package global
my %fields = (
name => undef,
age => undef,
peers => undef,
);
sub new {
my $that = shift;
my $class = ref($that) || $that;
my $self = {
_permitted => \%fields,
%fields,
};
bless $self, $class;
return $self;
}
|
|
If we wanted our record to have default values, we could fill those in where current we have undef
in the %fields hash.
Notice how we saved a reference to our class data on the object itself? Remember that it's
important to access class data through the object itself instead of having any method reference
%fields directly, or else you won't have a decent inheritance.
The real magic, though, is going to reside in our proxy method, which will handle all calls
to undefined methods for objects of class Person (or subclasses of Person). It has to be called
AUTOLOAD. Again, it's all caps because it's called for us implicitly by Perl itself, not by a
user directly.
sub AUTOLOAD {
my $self = shift;
my $type = ref($self)
or croak "$self is not an object";
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
unless (exists $self->{_permitted}->{$name} ) {
croak "Can't access `$name' field in class $type";
}
if (@_) {
return $self->{$name} = shift;
} else {
return $self->{$name};
}
}
|
|
Pretty nifty, eh? All we have to do to add new data fields is modify %fields. No new
functions need be written.
I could have avoided the _permitted field entirely, but I wanted to demonstrate
how to store a reference to class data on the object so you wouldn't have to access that class
data directly from an object method.
But what about inheritance? Can we define our Employee class similarly? Yes, so long as we're
careful enough.
Here's how to be careful:
package Employee;
use Person;
use strict;
our @ISA = qw(Person);
my %fields = (
id => undef,
salary => undef,
);
sub new {
my $that = shift;
my $class = ref($that) || $that;
my $self = bless $that->SUPER::new(), $class;
my($element);
foreach $element (keys %fields) {
$self->{_permitted}->{$element} = $fields{$element};
}
@{$self}{keys %fields} = values %fields;
return $self;
}
|
|
Once we've done this, we don't even need to have an AUTOLOAD function in the Employee
package, because we'll grab Person's version of that via inheritance, and it will all work out
just fine.
|
|