the file Person.pm. If it were called a Happy::Person class, it would
be stored in the file Happy/Person.pm, and its package would become
Happy::Person instead of just Person. (On a personal computer not
-running Unix or Plan 9, but something like MacOS or VMS, the directory
+running Unix or Plan 9, but something like Mac OS or VMS, the directory
separator may be different, but the principle is the same.) Do not assume
any formal relationship between modules based on their directory names.
This is merely a grouping convenience, and has no effect on inheritance,
=head2 Object Representation
By far the most common mechanism used in Perl to represent a Pascal
-record, a C struct, or a C++ class an anonymous hash. That's because a
+record, a C struct, or a C++ class is an anonymous hash. That's because a
hash has an arbitrary number of data fields, each conveniently accessed by
an arbitrary name of your own devising.
PEERS => [ "Norbert", "Rhys", "Phineas"],
};
-And so you could get at C<$rec-E<gt>{NAME}> to find "Jason", or
-C<@{ $rec-E<gt>{PEERS} }> to get at "Norbert", "Rhys", and "Phineas".
+And so you could get at C<< $rec->{NAME} >> to find "Jason", or
+C<< @{ $rec->{PEERS} } >> to get at "Norbert", "Rhys", and "Phineas".
(Have you ever noticed how many 23-year-old programmers seem to
be named "Jason" these days? :-)
By not assuming our own class as the default second argument and instead
using the class passed into us, we make our constructor inheritable.
-While we're at it, let's make our constructor a bit more flexible.
-Rather than being uniquely a class method, we'll set it up so that
-it can be called as either a class method I<or> an object
-method. That way you can say:
-
- $me = Person->new();
- $him = $me->new();
-
-To do this, all we have to do is check whether what was passed in
-was a reference or not. If so, we were invoked as an object method,
-and we need to extract the package (class) using the ref() function.
-If not, we just use the string passed in as the package name
-for blessing our referent.
-
sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
+ my $class = shift;
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
Destruction happens automatically via Perl's garbage collection (GC)
system, which is a quick but somewhat lazy reference-based GC system.
To know what to call, Perl insists that the destructor be named DESTROY.
+Perl's notion of the right time to call a destructor is not well-defined
+currently, which is why your destructors should not rely on when they are
+called.
Why is DESTROY in all caps? Perl on occasion uses purely uppercase
function names as a convention to indicate that the function will
Perl doesn't impose restrictions on who gets to use which methods.
The public-versus-private distinction is by convention, not syntax.
-(Well, unless you use the Alias module described below in
-L</"Data Members as Variables">.) Occasionally you'll see method names beginning or ending
+(Well, unless you use the Alias module described below in
+L<Data Members as Variables>.) Occasionally you'll see method names beginning or ending
with an underscore or two. This marking is a convention indicating
that the methods are private to that class alone and sometimes to its
closest acquaintances, its immediate subclasses. But this distinction
sub exclaim {
my $self = shift;
return sprintf "Hi, I'm %s, age %d, working with %s",
- $self->{NAME}, $self->{AGE}, join(", ", $self->{PEERS});
+ $self->{NAME}, $self->{AGE}, join(", ", @{$self->{PEERS}});
}
Or maybe even one like this:
}
But since these methods are all executing in the class itself, this
-may not be critical. There are trade-offs to be made. Using direct
+may not be critical. There are tradeoffs to be made. Using direct
hash access is faster (about an order of magnitude faster, in fact), and
it's more convenient when you want to interpolate in strings. But using
methods (the external interface) internally shields not just the users of
changes to your Person::new() constructor:
sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
+ my $class = shift;
my $self = {};
$Census++;
$self->{NAME} = undef;
Notice how there's no memory to deallocate in the destructor? That's
something that Perl takes care of for you all by itself.
+Alternatively, you could use the Class::Data::Inheritable module from
+CPAN.
+
+
=head2 Accessing Class Data
It turns out that this is not really a good way to go about handling
to perl version 5.004 we'll have to quote the field name.)
sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
+ my $class = shift;
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
}
What happens if a derived class (which we'll call Employee) inherits
-methods from this Person base class? Then C<Employee-E<gt>debug()>, when called
+methods from this Person base class? Then C<< Employee->debug() >>, when called
as a class method, manipulates $Person::Debugging not $Employee::Debugging.
=head2 Class Destructors
a full name field this way:
sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
+ my $class = shift;
my $self = {};
$self->{FULLNAME} = Fullname->new();
$self->{AGE} = undef;
use strict;
sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
+ my $class = shift;
my $self = {
TITLE => undef,
CHRISTIAN => undef,
$him->fullname->title("St");
$him->age(1);
- printf "%s is really %s.\n", $him->name, $him->fullname;
+ printf "%s is really %s.\n", $him->name, $him->fullname->as_string;
printf "%s's age: %d.\n", $him->name, $him->age;
$him->happy_birthday;
printf "%s's age: %d.\n", $him->name, $him->age;
then the new derived class can be used as a drop-in replacement for the
old one. This means you should be able to write a program like this:
- use Employee
+ use Employee;
my $empl = Employee->new();
$empl->name("Jason");
$empl->age(23);
a method? Well, in principle, yes. A method is just a function that
expects as its first argument a class name (package) or object
(blessed reference). Person::new() is the function that both the
-C<Person-E<gt>new()> method and the C<Employee-E<gt>new()> method end
+C<< Person->new() >> method and the C<< Employee->new() >> method end
up calling. Understand that while a method call looks a lot like a
function call, they aren't really quite the same, and if you treat them
as the same, you'll very soon be left with nothing but broken programs.
This way it starts looking in my class's @ISA. This only makes sense
from I<within> a method call, though. Don't try to access anything
in SUPER:: from anywhere else, because it doesn't exist outside
-an overridden method call.
+an overridden method call. Note that C<SUPER> refers to the superclass of
+the current package, I<not> to the superclass of C<$self>.
Things are getting a bit complicated here. Have we done anything
we shouldn't? As before, one way to test whether we're designing
$boss->age(47);
$boss->peers("Frank", "Felipe", "Faust");
- printf "%s is age %d.\n", $boss->fullname, $boss->age;
+ printf "%s is age %d.\n", $boss->fullname->as_string, $boss->age;
printf "His peers are: %s\n", join(", ", $boss->peers);
Running it, we see that we're still ok. If you'd like to dump out your
fix up Employee::new() this way:
sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
+ my $class = shift;
my $self = $class->SUPER::new();
$self->{SALARY} = undef;
$self->{ID} = undef;
The way it works is actually pretty simple: just put more than one package
name in your @ISA array. When it comes time for Perl to go finding
methods for your object, it looks at each of these packages in order.
-Well, kinda. It's actually a fully recursive, depth-first order.
+Well, kinda. It's actually a fully recursive, depth-first order by
+default (see L<mro> for alternate method resolution orders).
Consider a bunch of @ISA arrays like this:
@First::ISA = qw( Alpha );
whatever you felt like into it.
However, as of version 5.004 (or some subversive releases, like 5.003_08),
-UNIVERSAL has some methods in it already. These are built-in to your Perl
+UNIVERSAL has some methods in it already. These are builtin to your Perl
binary, so they don't take any extra time to load. Predefined methods
include isa(), can(), and VERSION(). isa() tells you whether an object or
class "is" another one without having to traverse the hierarchy yourself:
If you wanted to add version checking to your Person class explained
above, just add this to Person.pm:
- use vars qw($VERSION);
- $VERSION = '1.1';
+ our $VERSION = '1.1';
-and then in Employee.pm could you can say
+and then in Employee.pm you can say
- use Employee 1.1;
+ use Person 1.1;
And it would make sure that you have at least that version number or
higher available. This is not the same as loading in that exact version
number. No mechanism currently exists for concurrent installation of
multiple versions of a module. Lamentably.
+=head2 Deeper UNIVERSAL details
+
+It is also valid (though perhaps unwise in most cases) to put other
+packages' names in @UNIVERSAL::ISA. These packages will also be
+implicitly inherited by all classes, just as UNIVERSAL itself is.
+However, neither UNIVERSAL nor any of its parents from the @ISA tree
+are explicit base classes of all objects. To clarify, given the
+following:
+
+ @UNIVERSAL::ISA = ('REALLYUNIVERSAL');
+
+ package REALLYUNIVERSAL;
+ sub special_method { return "123" }
+
+ package Foo;
+ sub normal_method { return "321" }
+
+Calling Foo->special_method() will return "123", but calling
+Foo->isa('REALLYUNIVERSAL') or Foo->isa('UNIVERSAL') will return
+false.
+
+If your class is using an alternate mro like C3 (see
+L<mro>), method resolution within UNIVERSAL / @UNIVERSAL::ISA will
+still occur in the default depth-first left-to-right manner,
+after the class's C3 mro is exhausted.
+
+All of the above is made more intuitive by realizing what really
+happens during method lookup, which is roughly like this
+ugly pseudo-code:
+
+ get_mro(class) {
+ # recurses down the @ISA's starting at class,
+ # builds a single linear array of all
+ # classes to search in the appropriate order.
+ # The method resolution order (mro) to use
+ # for the ordering is whichever mro "class"
+ # has set on it (either default (depth first
+ # l-to-r) or C3 ordering).
+ # The first entry in the list is the class
+ # itself.
+ }
+
+ find_method(class, methname) {
+ foreach $class (get_mro(class)) {
+ if($class->has_method(methname)) {
+ return ref_to($class->$methname);
+ }
+ }
+ foreach $class (get_mro(UNIVERSAL)) {
+ if($class->has_method(methname)) {
+ return ref_to($class->$methname);
+ }
+ }
+ return undef;
+ }
+
+However the code that implements UNIVERSAL::isa does not
+search in UNIVERSAL itself, only in the package's actual
+@ISA.
+
=head1 Alternate Object Representations
Nothing requires objects to be implemented as hash references. An object
package Person;
sub new {
- my $that = shift;
- my $class = ref($that) || $that;
+ my $class = shift;
my $self = {
NAME => undef,
AGE => undef,
package Person;
use Carp;
- use vars qw($AUTOLOAD); # it's a package global
+ our $AUTOLOAD; # it's a package global
my %fields = (
name => undef,
);
sub new {
- my $that = shift;
- my $class = ref($that) || $that;
+ my $class = shift;
my $self = {
_permitted => \%fields,
%fields,
I could have avoided the C<_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
+object so you wouldn't have to access that class data
directly from an object method.
=head2 Inherited Autoloaded Data Methods
package Employee;
use Person;
use strict;
- use vars qw(@ISA);
- @ISA = qw(Person);
+ our @ISA = qw(Person);
my %fields = (
id => undef,
);
sub new {
- my $that = shift;
- my $class = ref($that) || $that;
- my $self = bless $that->SUPER::new(), $class;
+ my $class = shift;
+ my $self = $class->SUPER::new();
my($element);
foreach $element (keys %fields) {
$self->{_permitted}->{$element} = $fields{$element};
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::Template and Alias. These and other related metaclasses can be
+Class::Struct and Alias. These and other related metaclasses can be
found in the modules directory on CPAN.
-=head2 Class::Template
+=head2 Class::Struct
-One of the older ones is Class::Template. In fact, its syntax and
+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
create a class to provide a record-like data object, you yourself have
to define a new() method, plus separate data-access methods for each of
that record's fields. You'll quickly become bored with this process.
-The Class::Template::struct() function alleviates this tedium.
+The Class::Struct::struct() function alleviates this tedium.
Here's a simple example of using it:
- use Class::Template qw(struct);
+ use Class::Struct qw(struct);
use Jobbie; # user-defined; see below
struct 'Fred' => {
one => '$',
many => '@',
- profession => Jobbie, # calls Jobbie->new()
+ profession => 'Jobbie', # does not call Jobbie->new()
};
- $ob = Fred->new;
+ $ob = Fred->new(profession => Jobbie->new());
$ob->one("hmmmm");
$ob->many(0, "here");
user-defined types (classes). User types will be initialized by calling
that class's new() method.
+Take care that the C<Jobbie> object is not created automatically by the
+C<Fred> class's new() method, so you should specify a C<Jobbie> object
+when you create an instance of C<Fred>.
+
Here's a real-world example of using struct generation. Let's say you
wanted to override Perl's idea of gethostbyname() and gethostbyaddr() so
that they would return objects that acted like C structures. We don't
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::Template module.
+Here's how to do this using the Class::Struct module.
The crux is going to be this call:
struct 'Net::hostent' => [ # note bracket
addr_list => '@',
};
-and then Class::Template would have used an anonymous hash as the object
+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,
BEGIN {
use Exporter ();
- use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
- @ISA = qw(Exporter);
- @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 ] );
+ our @EXPORT = qw(gethostbyname gethostbyaddr gethost);
+ our @EXPORT_OK = qw(
+ $h_name @h_aliases
+ $h_addrtype $h_length
+ @h_addr_list $h_addr
+ );
+ our %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] );
}
- use vars @EXPORT_OK;
+ our @EXPORT_OK;
- use Class::Template qw(struct);
+ # Class::Struct forbids use of @ISA
+ sub import { goto &Exporter::import }
+
+ use Class::Struct qw(struct);
struct 'Net::hostent' => [
name => '$',
aliases => '@',
sub populate (@) {
return unless @_;
- my $hob = new(); # Class::Template made this!
+ my $hob = new(); # Class::Struct made this!
$h_name = $hob->[0] = $_[0];
@h_aliases = @{ $hob->[1] } = split ' ', $_[1];
$h_addrtype = $hob->[2] = $_[2];
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, and short-cut function call via C<&whatever>.
-These all mostly make sense from the perspective of a traditional module,
-but as you can see, we can also use them in an object module.
+function prototyping, short-cut function call via C<&whatever>, and
+function replacement with C<goto &whatever>. These all mostly make
+sense from the perspective of a traditional module, but as you can see,
+we can also use them in an object module.
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 built-in function.
+because they affect the compilation and change a builtin function.
They also have the type names that a C programmer would most expect.
=head2 Data Members as Variables
# this is the same as before...
sub new {
- my $that = shift;
- my $class = ref($that) || $that;
+ my $class = shift;
my $self = {
NAME => undef,
AGE => undef,
}
use Alias qw(attr);
- use vars qw($NAME $AGE $PEERS);
+ our ($NAME, $AGE, $PEERS);
sub name {
my $self = attr shift;
return ++$AGE;
}
-The need for the C<use vars> declaration is because what Alias does
+The need for the C<our> declaration is because what Alias does
is play with package globals with the same name as the fields. To use
-globals while C<use strict> is in effect, you have to pre-declare them.
+globals while C<use strict> is in effect, you have to predeclare them.
These package variables are localized to the block enclosing the attr()
call just as if you'd used a local() on them. However, that means that
they're still considered global variables with temporary values, just
as with any other local().
It would be nice to combine Alias with
-something like Class::Template or Class::MethodMaker.
+something like Class::Struct or Class::MethodMaker.
-=head2 NOTES
+=head1 NOTES
=head2 Object Terminology
call a class method (one expecting a string argument) on an
object (one expecting a reference), or vice versa.
-Z<>From the C++ perspective, all methods in Perl are virtual.
+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 built-in and user-defined
+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
=head1 SEE ALSO
-The following man pages will doubtless provide more
+The following manpages will doubtless provide more
background for this one:
L<perlmod>,
L<perlref>,
and
L<overload>.
-=head1 COPYRIGHT
+L<perlboot> is a kinder, gentler introduction to object-oriented
+programming.
+
+L<perltooc> provides more detail on class data.
-I I<really> hate to have to say this, but recent unpleasant
-experiences have mandated its inclusion:
-
- Copyright 1996 Tom Christiansen. All Rights Reserved.
-
-This work derives in part from the second edition of I<Programming Perl>.
-Although destined for release as a man page with the standard Perl
-distribution, it is not public domain (nor is any of Perl and its docset:
-publishers beware). It's expected to someday make its way into a revision
-of the Camel Book. While it is copyright by me with all rights reserved,
-permission is granted to freely distribute verbatim copies of this
-document provided that no modifications outside of formatting be made,
-and that this notice remain intact. You are permitted and encouraged to
-use its code and derivatives thereof in your own source code for fun or
-for profit as you see fit. But so help me, if in six months I find some
-book out there with a hacked-up version of this material in it claiming to
-be written by someone else, I'll tell all the world that you're a jerk.
-Furthermore, your lawyer will meet my lawyer (or O'Reilly's) over lunch
-to arrange for you to receive your just deserts. Count on it.
+Some modules which might prove interesting are Class::Accessor,
+Class::Class, Class::Contract, Class::Data::Inheritable,
+Class::MethodMaker and Tie::SecureHash
+
+
+=head1 AUTHOR AND COPYRIGHT
+
+Copyright (c) 1997, 1998 Tom Christiansen
+All rights reserved.
+
+This documentation is free; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+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.
+
+=head1 COPYRIGHT
=head2 Acknowledgments