[big snip]
That's why carelessly written classes that access their objects all over
the place are useless for inheritance. A good class defines a handful
of accessors and then never touches the object again. Then most of the
useful stuff *can* be inherited.
If the inheriting class has an object of the base class a component,
^^^^^^^^^^^^^^^^^^^^^^
That should have been "...base class *as* a component", sorry.
(so the is-a relation is based on a has-a relation), even accessors
can be made inheritable with a little trick the base class must provide.
I'll post a rewrite of my example a little later.
Okay, here goes.
The code for the base class is mostly unchanged. The only difference is
that it inserts a do-nothing method (one that just returns its object) into
all of its accessors (not in other methods). So if the accessor was
sub alpha { $_[ 0]->[ 0] }
it becomes
sub alpha { $_[ 0]->parent->[ 0] }
# ...
sub parent { shift }
Obviously, this doesn't change the behavior of the class, except for
wasting some time (but we're doing OO here, Efficiency is down the hall).
So clients who don't know about the change continue to function.
The no-op is called like the class (de-emphasized by lower-casing). It
could, of course, be called anything, but that is a good choice from the
clients point of view if the client chooses to use the facility.
It does that by defining a field that holds an object of class Parent
and calling the accessor to that field "parent", so it overrides that
method in the base class.
sub new {
# ...
bless {
parent => Parent->new( ...),
# ...
}, $class;
}
sub parent { $_[ 0]->{ parent} }
The effect is that the base class, when it comes to accessing an object,
interpolates the 'parent' method and sees an object of its own class which
it can handle.
This alone allows the client to inherit from Parent, that is, it can
put "Parent" on @ISA and all Parent methods will work using the parent
component. Thus it must override only one method. It can go on to
override specific other methods to modify the behavior.
So here is my original example re-written this way. The class Client_orig
is how it must be done (well, one way) without the ->parent method. It
still works with the modified Parent, but the modified Client is more
orderly.
Anno
#!/usr/local/bin/perl
use strict; use warnings; $| = 1;
use Vi::QuickFix;
my $c = Client->new( 5, 6, 7);
print "$_ -> ", $c->$_, "\n" for qw( alpha beta gamma delta total);
##########################################################################
package Parent;
sub new {
my $class = shift;
my ( $alpha, $beta) = @_;
bless [ $alpha, $beta], $class;
}
sub parent { shift }
sub alpha { $_[ 0]->parent->[ 0] }
sub beta { $_[ 0]->parent->[ 1] }
sub total { $_[ 0]->alpha + $_[ 0]->beta }
##########################################################################
package Client;
BEGIN { our @ISA = 'Parent' }
sub new {
my $class = shift;
my ( $alpha, $gamma, $delta) @_;
bless {
parent => Parent->new( $alpha, undef),
gamma => $gamma,
delta => $delta,
}, $class,
}
sub parent { $_[ 0]->{ parent} }
sub gamma { $_[ 0]->{ gamma} }
sub delta { $_[ 0]->{ delta} }
sub beta { $_[ 0]->gamma + $_[ 0]->delta }
##########################################################################
package Client_orig;
BEGIN { our @ISA = 'Parent' }
sub new {
my $class = shift;
my ( $alpha, $gamma, $delta) = @_;
bless {
alpha => $alpha,
gamma => $gamma,
delta => $delta,
}, $class;
}
sub alpha { $_[ 0]->{ alpha} }
sub gamma { $_[ 0]->{ gamma} }
sub delta { $_[ 0]->{ delta} }
sub beta { $_[ 0]->gamma + $_[ 0]->delta }
__END__