good chemistry is complicated, and a little bit messy -LW |
|
PerlMonks |
perlman:perltoot2by gods (Initiate) |
on Aug 25, 1999 at 07:13 UTC ( [id://420]=perlman: print w/replies, xml ) | Need Help?? |
perltoot2Current Perl documentation can be found at perldoc.perl.org. Here is our local, out-dated (pre-5.6) version: Autoloaded Data MethodsYou 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 Here's what the module initialization code and class constructor will look like when taking this approach:
package Person; use Carp; use vars qw($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
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 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
Inherited Autoloaded Data MethodsBut 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; use vars qw(@ISA); @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.
Metaclassical ToolsEven though proxy methods can provide a more convenient approach to making more struct-like classes than tediously coding up data methods as functions, it still leaves a bit to be desired. For one thing, it means you have to handle bogus calls that you don't mean to trap via your proxy. It also means you have to be quite careful when dealing with inheritance, as detailed above. Perl programmers have responded to this by creating several different class construction classes. These metaclasses are classes that create other classes. A couple worth looking at are Class::Struct and Alias. These and other related metaclasses can be found in the modules directory on CPAN.
Class::Struct
One of the older ones is Class::Struct. In fact, its syntax and interface were sketched out long before perl5 even solidified into a real thing. What it does is provide you a way to ``declare'' a class as having objects whose fields are of a specific type. The function that does this is called, not surprisingly enough,
Here's a simple example of using it:
use Class::Struct qw(struct); use Jobbie; # user-defined; see below
struct 'Fred' => { one => '$', many => '@', profession => Jobbie, # calls Jobbie->new() };
$ob = Fred->new; $ob->one("hmmmm");
$ob->many(0, "here"); $ob->many(1, "you"); $ob->many(2, "go"); print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);
You can declare types in the struct to be basic Perl types, or user-defined types (classes). User types will be initialized by calling that class's
Here's a real-world example of using struct generation. Let's say you wanted to override Perl's idea of
use Socket; use Net::hostent; $h = gethostbyname("perl.com"); # object return printf "perl.com's real name is %s, address %s\n", $h->name, inet_ntoa($h->addr); Here's how to do this using the Class::Struct module. The crux is going to be this call:
struct 'Net::hostent' => [ # note bracket name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ];
Which creates object methods of those names and types. It even creates a
We could also have implemented our object this way:
struct 'Net::hostent' => { # note brace name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', }; and then Class::Struct would have used an anonymous hash as the object type, instead of an anonymous array. The array is faster and smaller, but the hash works out better if you eventually want to do inheritance. Since for this struct-like object we aren't planning on inheritance, this time we'll opt for better speed and size over better flexibility. Here's the whole implementation:
package Net::hostent; use strict;
BEGIN { use Exporter (); use vars qw(@EXPORT @EXPORT_OK %EXPORT_TAGS); @EXPORT = qw(gethostbyname gethostbyaddr gethost); @EXPORT_OK = qw( $h_name @h_aliases $h_addrtype $h_length @h_addr_list $h_addr ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } use vars @EXPORT_OK;
# Class::Struct forbids use of @ISA sub import { goto &Exporter::import }
use Class::Struct qw(struct); struct 'Net::hostent' => [ name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ];
sub addr { shift->addr_list->[0] }
sub populate (@) { return unless @_; my $hob = new(); # Class::Struct made this! $h_name = $hob->[0] = $_[0]; @h_aliases = @{ $hob->[1] } = split ' ', $_[1]; $h_addrtype = $hob->[2] = $_[2]; $h_length = $hob->[3] = $_[3]; $h_addr = $_[4]; @h_addr_list = @{ $hob->[4] } = @_[ (4 .. $#_) ]; return $hob; }
sub gethostbyname ($) { populate(CORE::gethostbyname(shift)) }
sub gethostbyaddr ($;$) { my ($addr, $addrtype); $addr = shift; require Socket unless @_; $addrtype = @_ ? shift : Socket::AF_INET(); populate(CORE::gethostbyaddr($addr, $addrtype)) }
sub gethost($) { if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) { require Socket; &gethostbyaddr(Socket::inet_aton(shift)); } else { &gethostbyname; } }
1;
We've snuck in quite a fair bit of other concepts besides just dynamic
class creation, like overriding core functions, import/export bits,
function prototyping, short-cut function call via You can look at other object-based, struct-like overrides of core functions in the 5.004 release of Perl in File::stat, Net::hostent, Net::netent, Net::protoent, Net::servent, Time::gmtime, Time::localtime, User::grent, and User::pwent. These modules have a final component that's all lowercase, by convention reserved for compiler pragmas, because they affect the compilation and change a builtin function. They also have the type names that a C programmer would most expect.
Data Members as VariablesIf you're used to C++ objects, then you're accustomed to being able to get at an object's data members as simple variables from within a method. The Alias module provides for this, as well as a good bit more, such as the possibility of private methods that the object can call but folks outside the class cannot. Here's an example of creating a Person using the Alias module. When you update these magical instance variables, you automatically update value fields in the hash. Convenient, eh?
package Person;
# this is the same as before... sub new { my $that = shift; my $class = ref($that) || $that; my $self = { NAME => undef, AGE => undef, PEERS => [], }; bless($self, $class); return $self; }
use Alias qw(attr); use vars qw($NAME $AGE $PEERS);
sub name { my $self = attr shift; if (@_) { $NAME = shift; } return $NAME; }
sub age { my $self = attr shift; if (@_) { $AGE = shift; } return $AGE; }
sub peers { my $self = attr shift; if (@_) { @PEERS = @_; } return @PEERS; }
sub exclaim { my $self = attr shift; return sprintf "Hi, I'm %s, age %d, working with %s", $NAME, $AGE, join(", ", @PEERS); }
sub happy_birthday { my $self = attr shift; return ++$AGE; }
The need for the It would be nice to combine Alias with something like Class::Struct or Class::MethodMaker.
NOTES
Object TerminologyIn the various OO literature, it seems that a lot of different words are used to describe only a few different concepts. If you're not already an object programmer, then you don't need to worry about all these fancy words. But if you are, then you might like to know how to get at the same concepts in Perl. For example, it's common to call an object an instance of a class and to call those objects' methods instance methods. Data fields peculiar to each object are often called instance data or object attributes, and data fields common to all members of that class are class data, class attributes, or static data members. Also, base class, generic class, and superclass all describe the same notion, whereas derived class, specific class, and subclass describe the other related one. C++ programmers have static methods and virtual methods, but Perl only has class methods and object methods. Actually, Perl only has methods. Whether a method gets used as a class or object method is by usage only. You could accidentally call a class method (one expecting a string argument) on an object (one expecting a reference), or vice versa. From the C++ perspective, all methods in Perl are virtual. This, by the way, is why they are never checked for function prototypes in the argument list as regular builtin and user-defined functions can be. Because a class is itself something of an object, Perl's classes can be taken as describing both a ``class as meta-object'' (also called object factory) philosophy and the ``class as type definition'' (declaring behaviour, not defining mechanism) idea. C++ supports the latter notion, but not the former.
SEE ALSOThe following manpages will doubtless provide more background for this one: the perlmod manpage, the perlref manpage, the perlobj manpage, the perlbot manpage, the perltie manpage, and the overload manpage.
AUTHOR AND COPYRIGHTCopyright (c) 1997, 1998 Tom Christiansen All rights reserved. When included as part of the Standard Version of Perl, or as part of its complete documentation whether printed or otherwise, this work may be distributed only under the terms of Perl's Artistic License. Any distribution of this file or derivatives thereof outside of that package require that special arrangements be made with copyright holder. Irrespective of its distribution, all code examples in this file are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.
COPYRIGHT
AcknowledgmentsThanks to Larry Wall, Roderick Schertler, Gurusamy Sarathy, Dean Roehrich, Raphael Manfredi, Brent Halsey, Greg Bacon, Brad Appleton, and many others for their helpful comments. Return to the Library |
|