|
What about "class data", data items common to each object in a class? What would
you want that for? Well, in your Person class, you might like to keep track of the total people
alive. How do you implement that?
You could make it a global variable called $Person::Census. But about only reason
you'd do that would be if you wanted people to be able to get at your class data
directly. They could just say $Person::Census and play around with it. Maybe this is ok in your
design scheme. You might even conceivably want to make it an exported variable. To be
exportable, a variable must be a (package) global. If this were a traditional module rather than
an object-oriented one, you might do that.
While this approach is expected in most traditional modules, it's generally considered rather
poor form in most object modules. In an object module, you should set up a protective veil to
separate interface from implementation. So provide a class method to access class data just as
you provide object methods to access object data.
So, you could still keep $Census as a package global and rely upon others to honor the
contract of the module and therefore not play around with its implementation. You could even be
supertricky and make $Census a tied object as described in perltie, thereby intercepting all
accesses.
But more often than not, you just want to make your class data a file-scoped lexical. To do
so, simply put this at the top of the file:
Even though the scope of a my() normally expires when the block in which it was declared is
done (in this case the whole file being required or used), Perl's deep binding of lexical
variables guarantees that the variable will not be deallocated, remaining accessible to
functions declared within that scope. This doesn't work with global variables given temporary
values via local(), though.
Irrespective of whether you leave $Census a package global or make it instead a file-scoped
lexical, you should make these changes to your Person::new() constructor:
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
$Census++;
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
bless ($self, $class);
return $self;
}
sub population {
return $Census;
}
|
|
Now that we've done this, we certainly do need a destructor so that when Person is destroyed,
the $Census goes down. Here's how this could be done:
sub DESTROY { --$Census }
|
|
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.
It turns out that this is not really a good way to go about handling class data. A good
scalable rule is that you must never reference class data directly from an object method.
Otherwise you aren't building a scalable, inheritable class. The object must be the rendezvous
point for all operations, especially from an object method. The globals (class data) would in
some sense be in the "wrong" package in your derived classes. In Perl, methods execute
in the context of the class they were defined in, not that of the object that triggered
them. Therefore, namespace visibility of package globals in methods is unrelated to inheritance.
Got that? Maybe not. Ok, let's say that some other class "borrowed" (well,
inherited) the DESTROY method as it was defined above. When those objects are destroyed, the
original $Census variable will be altered, not the one in the new class's package namespace.
Perhaps this is what you want, but probably it isn't.
Here's how to fix this. We'll store a reference to the data in the value accessed by the hash
key "_CENSUS". Why the underscore? Well, mostly because an initial underscore already
conveys strong feelings of magicalness to a C programmer. It's really just a mnemonic device to
remind ourselves that this field is special and not to be used as a public data member in the
same way that NAME, AGE, and PEERS are. (Because we've been developing this code under the
strict pragma, prior to perl version 5.004 we'll have to quote the field name.)
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
# "private" data
$self->{"_CENSUS"} = \$Census;
bless ($self, $class);
++ ${ $self->{"_CENSUS"} };
return $self;
}
sub population {
my $self = shift;
if (ref $self) {
return ${ $self->{"_CENSUS"} };
} else {
return $Census;
}
}
sub DESTROY {
my $self = shift;
-- ${ $self->{"_CENSUS"} };
}
|
|
It's common for a class to have a debugging mechanism. For example, you might want to see
when objects are created or destroyed. To do that, add a debugging variable as a file-scoped
lexical. For this, we'll pull in the standard Carp module to emit our warnings and fatal
messages. That way messages will come out with the caller's filename and line number instead of
our own; if we wanted them to be from our own perspective, we'd just use die() and warn()
directly instead of croak() and carp() respectively.
use Carp;
my $Debugging = 0;
|
|
Now add a new class method to access the variable.
sub debug {
my $class = shift;
if (ref $class) { confess "Class method called as object method" }
unless (@_ == 1) { confess "usage: CLASSNAME->debug(level)" }
$Debugging = shift;
}
|
|
Now fix up DESTROY to murmur a bit as the moribund object expires:
sub DESTROY {
my $self = shift;
if ($Debugging) { carp "Destroying $self " . $self->name }
-- ${ $self->{"_CENSUS"} };
}
|
|
One could conceivably make a per-object debug state. That way you could call both of these:
Person->debug(1); # entire class
$him->debug(1); # just this object
|
|
To do so, we need our debugging method to be a "bimodal" one, one that works on
both classes and objects. Therefore, adjust the debug() and DESTROY methods as follows:
sub debug {
my $self = shift;
confess "usage: thing->debug(level)" unless @_ == 1;
my $level = shift;
if (ref($self)) {
$self->{"_DEBUG"} = $level; # just myself
} else {
$Debugging = $level; # whole class
}
}
sub DESTROY {
my $self = shift;
if ($Debugging || $self->{"_DEBUG"}) {
carp "Destroying $self " . $self->name;
}
-- ${ $self->{"_CENSUS"} };
}
|
|
What happens if a derived class (which we'll call Employee) inherits methods from this Person
base class? Then Employee->debug(), when called as a class method, manipulates $Person::Debugging
not $Employee::Debugging.
The object destructor handles the death of each distinct object. But sometimes you want a bit
of cleanup when the entire class is shut down, which currently only happens when the program
exits. To make such a class destructor, create a function in that class's package named
END. This works just like the END function in traditional modules, meaning that it gets called
whenever your program exits unless it execs or dies of an uncaught signal. For example,
sub END {
if ($Debugging) {
print "All persons are going away now.\n";
}
}
|
|
When the program exits, all the class destructors (END functions) are be called in the
opposite order that they were loaded in (LIFO order).
And there you have it: we've just shown you the implementation of this Person class.
Its interface would be its documentation. Usually this means putting it in pod
("plain old documentation") format right there in the same file. In our Person
example, we would place the following docs anywhere in the Person.pm file. Even though it looks
mostly like code, it's not. It's embedded documentation such as would be used by the pod2man,
pod2html, or pod2text programs. The Perl compiler ignores pods entirely, just as the translators
ignore code. Here's an example of some pods describing the informal interface:
=head1 NAME
Person - class to implement people
=head1 SYNOPSIS
use Person;
#################
# class methods #
#################
$ob = Person->new;
$count = Person->population;
#######################
# object data methods #
#######################
### get versions ###
$who = $ob->name;
$years = $ob->age;
@pals = $ob->peers;
### set versions ###
$ob->name("Jason");
$ob->age(23);
$ob->peers( "Norbert", "Rhys", "Phineas" );
########################
# other object methods #
########################
$phrase = $ob->exclaim;
$ob->happy_birthday;
=head1 DESCRIPTION
The Person class implements dah dee dah dee dah....
|
|
That's all there is to the matter of interface versus implementation. A programmer who opens
up the module and plays around with all the private little shiny bits that were safely locked up
behind the interface contract has voided the warranty, and you shouldn't worry about their fate.
|