perltoot.pod

来自「ARM上的如果你对底层感兴趣」· POD 代码 · 共 1,696 行 · 第 1/5 页

POD
1,696
字号

Ok, at the risk of confusing beginners and annoying OO gurus, it's
time to confess that Perl's object system includes that controversial
notion known as multiple inheritance, or MI for short.  All this means
is that rather than having just one parent class who in turn might
itself have a parent class, etc., that you can directly inherit from
two or more parents.  It's true that some uses of MI can get you into
trouble, although hopefully not quite so much trouble with Perl as with
dubiously-OO languages like C++.

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.
Consider a bunch of @ISA arrays like this:

    @First::ISA    = qw( Alpha );
    @Second::ISA   = qw( Beta );
    @Third::ISA    = qw( First Second );

If you have an object of class Third:

    my $ob = Third->new();
    $ob->spin();

How do we find a spin() method (or a new() method for that matter)?
Because the search is depth-first, classes will be looked up
in the following order: Third, First, Alpha, Second, and Beta.

In practice, few class modules have been seen that actually
make use of MI.  One nearly always chooses simple containership of
one class within another over MI.  That's why our Person
object I<contained> a Fullname object.  That doesn't mean
it I<was> one.

However, there is one particular area where MI in Perl is rampant:
borrowing another class's class methods.  This is rather common,
especially with some bundled "objectless" classes,
like Exporter, DynaLoader, AutoLoader, and SelfLoader.  These classes
do not provide constructors; they exist only so you may inherit their
class methods.  (It's not entirely clear why inheritance was done
here rather than traditional module importation.)

For example, here is the POSIX module's @ISA:

    package POSIX;
    @ISA = qw(Exporter DynaLoader);

The POSIX module isn't really an object module, but then,
neither are Exporter or DynaLoader.  They're just lending their
classes' behaviours to POSIX.

Why don't people use MI for object methods much?  One reason is that
it can have complicated side-effects.  For one thing, your inheritance
graph (no longer a tree) might converge back to the same base class.
Although Perl guards against recursive inheritance, merely having parents
who are related to each other via a common ancestor, incestuous though
it sounds, is not forbidden.  What if in our Third class shown above we
wanted its new() method to also call both overridden constructors in its
two parent classes?  The SUPER notation would only find the first one.
Also, what about if the Alpha and Beta classes both had a common ancestor,
like Nought?  If you kept climbing up the inheritance tree calling
overridden methods, you'd end up calling Nought::new() twice,
which might well be a bad idea.

=head2 UNIVERSAL: The Root of All Objects

Wouldn't it be convenient if all objects were rooted at some ultimate
base class?  That way you could give every object common methods without
having to go and add it to each and every @ISA.  Well, it turns out that
you can.  You don't see it, but Perl tacitly and irrevocably assumes
that there's an extra element at the end of @ISA: the class UNIVERSAL.
In version 5.003, there were no predefined methods there, but you could put
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 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:

   $has_io = $fd->isa("IO::Handle");
   $itza_handle = IO::Socket->isa("IO::Handle");

The can() method, called against that object or class, reports back
whether its string argument is a callable method name in that class.
In fact, it gives you back a function reference to that method:

   $his_print_method = $obj->can('as_string');

Finally, the VERSION method checks whether the class (or the object's
class) has a package global called $VERSION that's high enough, as in:

    Some_Module->VERSION(3.0);
    $his_vers = $ob->VERSION();

However, we don't usually call VERSION ourselves.  (Remember that an all
uppercase function name is a Perl convention that indicates that the
function will be automatically used by Perl in some way.)  In this case,
it happens when you say

    use Some_Module 3.0;

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';

and then in Employee.pm could you can say

    use Employee 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.

=head1 Alternate Object Representations

Nothing requires objects to be implemented as hash references.  An object
can be any sort of reference so long as its referent has been suitably
blessed.  That means scalar, array, and code references are also fair
game.

A scalar would work if the object has only one datum to hold.  An array
would work for most cases, but makes inheritance a bit dodgy because
you have to invent new indices for the derived classes.

=head2 Arrays as Objects

If the user of your class honors the contract and sticks to the advertised
interface, then you can change its underlying interface if you feel
like it.  Here's another implementation that conforms to the same
interface specification.  This time we'll use an array reference
instead of a hash reference to represent the object.

    package Person;
    use strict;

    my($NAME, $AGE, $PEERS) = ( 0 .. 2 );

    ############################################
    ## the object constructor (array version) ##
    ############################################
    sub new {
        my $self = [];
        $self->[$NAME]   = undef;  # this is unnecessary
        $self->[$AGE]    = undef;  # as is this
        $self->[$PEERS]  = [];     # but this isn't, really
        bless($self);
        return $self;
    }

    sub name {
        my $self = shift;
        if (@_) { $self->[$NAME] = shift }
        return $self->[$NAME];
    }

    sub age {
        my $self = shift;
        if (@_) { $self->[$AGE] = shift }
        return $self->[$AGE];
    }

    sub peers {
        my $self = shift;
        if (@_) { @{ $self->[$PEERS] } = @_ }
        return @{ $self->[$PEERS] };
    }

    1;  # so the require or use succeeds

You might guess that the array access would be a lot faster than the
hash access, but they're actually comparable.  The array is a I<little>
bit faster, but not more than ten or fifteen percent, even when you
replace the variables above like $AGE with literal numbers, like 1.
A bigger difference between the two approaches can be found in memory use.
A hash representation takes up more memory than an array representation
because you have to allocate memory for the keys as well as for the values.
However, it really isn't that bad, especially since as of version 5.004,
memory is only allocated once for a given hash key, no matter how many
hashes have that key.  It's expected that sometime in the future, even
these differences will fade into obscurity as more efficient underlying
representations are devised.

Still, the tiny edge in speed (and somewhat larger one in memory)
is enough to make some programmers choose an array representation
for simple classes.  There's still a little problem with
scalability, though, because later in life when you feel
like creating subclasses, you'll find that hashes just work
out better.

=head2 Closures as Objects

Using a code reference to represent an object offers some fascinating
possibilities.  We can create a new anonymous function (closure) who
alone in all the world can see the object's data.  This is because we
put the data into an anonymous hash that's lexically visible only to
the closure we create, bless, and return as the object.  This object's
methods turn around and call the closure as a regular subroutine call,
passing it the field we want to affect.  (Yes,
the double-function call is slow, but if you wanted fast, you wouldn't
be using objects at all, eh? :-)

Use would be similar to before:

    use Person;
    $him = Person->new();
    $him->name("Jason");
    $him->age(23);
    $him->peers( [ "Norbert", "Rhys", "Phineas" ] );
    printf "%s is %d years old.\n", $him->name, $him->age;
    print "His peers are: ", join(", ", @{$him->peers}), "\n";

but the implementation would be radically, perhaps even sublimely
different:

    package Person;

    sub new {
	 my $that  = shift;
	 my $class = ref($that) || $that;
	 my $self = {
	    NAME  => undef,
	    AGE   => undef,
	    PEERS => [],
	 };
	 my $closure = sub {
	    my $field = shift;
	    if (@_) { $self->{$field} = shift }
	    return    $self->{$field};
	};
	bless($closure, $class);
	return $closure;
    }

    sub name   { &{ $_[0] }("NAME",  @_[ 1 .. $#_ ] ) }
    sub age    { &{ $_[0] }("AGE",   @_[ 1 .. $#_ ] ) }
    sub peers  { &{ $_[0] }("PEERS", @_[ 1 .. $#_ ] ) }

    1;

Because this object is hidden behind a code reference, it's probably a bit
mysterious to those whose background is more firmly rooted in standard
procedural or object-based programming languages than in functional
programming languages whence closures derive.  The object
created and returned by the new() method is itself not a data reference
as we've seen before.  It's an anonymous code reference that has within
it access to a specific version (lexical binding and instantiation)
of the object's data, which are stored in the private variable $self.
Although this is the same function each time, it contains a different
version of $self.

When a method like C<$him-E<gt>name("Jason")> is called, its implicit
zeroth argument is the invoking object--just as it is with all method
calls.  But in this case, it's our code reference (something like a
function pointer in C++, but with deep binding of lexical variables).
There's not a lot to be done with a code reference beyond calling it, so
that's just what we do when we say C<&{$_[0]}>.  This is just a regular
function call, not a method call.  The initial argument is the string
"NAME", and any remaining arguments are whatever had been passed to the
method itself.

Once we're executing inside the closure that had been created in new(),
the $self hash reference suddenly becomes visible.  The closure grabs
its first argument ("NAME" in this case because that's what the name()
method passed it), and uses that string to subscript into the private
hash hidden in its unique version of $self.

Nothing under the sun will allow anyone outside the executing method to
be able to get at this hidden data.  Well, nearly nothing.  You I<could>
single step through the program using the debugger and find out the
pieces while you're in the method, but everyone else is out of luck.

There, if that doesn't excite the Scheme folks, then I just don't know
what will.  Translation of this technique into C++, Java, or any other
braindead-static language is left as a futile exercise for aficionados
of those camps.

You could even add a bit of nosiness via the caller() function and
make the closure refuse to operate unless called via its own package.
This would no doubt satisfy certain fastidious concerns of programming
police and related puritans.

If you were wondering when Hubris, the third principle virtue of a
programmer, would come into play, here you have it. (More seriously,
Hubris is just the pride in craftsmanship that comes from having written
a sound bit of well-designed code.)

=head1 AUTOLOAD: Proxy Methods

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 C<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.

=head2 Autoloaded Data Methods

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 C<_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".

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?