perltoot.pod
来自「ARM上的如果你对底层感兴趣」· POD 代码 · 共 1,696 行 · 第 1/5 页
POD
1,696 行
Some might argue that one should go at these this way:
sub exclaim {
my $self = shift;
return sprintf "Hi, I'm %s, age %d, working with %s",
$self->name, $self->age, join(", ", $self->peers);
}
sub happy_birthday {
my $self = shift;
return $self->age( $self->age() + 1 );
}
But since these methods are all executing in the class itself, this
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
your class but even you yourself from changes in your data representation.
=head1 Class Data
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 I<could> make it a global variable called $Person::Census. But about
only reason you'd do that would be if you I<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 I<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 L<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:
my $Census = 0;
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.
=head2 Accessing Class Data
It turns out that this is not really a good way to go about handling
class data. A good scalable rule is that I<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, I<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"} };
}
=head2 Debugging Methods
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 I<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 C<Employee-E<gt>debug()>, when called
as a class method, manipulates $Person::Debugging not $Employee::Debugging.
=head2 Class Destructors
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
I<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).
=head2 Documenting the Interface
And there you have it: we've just shown you the I<implementation> of this
Person class. Its I<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.
=head1 Aggregation
Suppose you later want to change the class to implement better names.
Perhaps you'd like to support both given names (called Christian names,
irrespective of one's religion) and family names (called surnames), plus
nicknames and titles. If users of your Person class have been properly
accessing it through its documented interface, then you can easily change
the underlying implementation. If they haven't, then they lose and
it's their fault for breaking the contract and voiding their warranty.
To do this, we'll make another class, this one called Fullname. What's
the Fullname class look like? To answer that question, you have to
first figure out how you want to use it. How about we use it this way:
$him = Person->new();
$him->fullname->title("St");
$him->fullname->christian("Thomas");
$him->fullname->surname("Aquinas");
$him->fullname->nickname("Tommy");
printf "His normal name is %s\n", $him->name;
printf "But his real name is %s\n", $him->fullname->as_string;
Ok. To do this, we'll change Person::new() so that it supports
a full name field this way:
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
$self->{FULLNAME} = Fullname->new();
$self->{AGE} = undef;
$self->{PEERS} = [];
$self->{"_CENSUS"} = \$Census;
bless ($self, $class);
++ ${ $self->{"_CENSUS"} };
return $self;
}
sub fullname {
my $self = shift;
return $self->{FULLNAME};
}
Then to support old code, define Person::name() this way:
sub name {
my $self = shift;
return $self->{FULLNAME}->nickname(@_)
|| $self->{FULLNAME}->christian(@_);
}
Here's the Fullname class. We'll use the same technique
of using a hash reference to hold data fields, and methods
by the appropriate name to access them:
package Fullname;
use strict;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?