in reply to Object Terminology

(Note: if you've not heard the term "shaggy dog story", here's a short description -- and I don't claim that mine is even remotely humorous)

Nice work. ++. And here's some food for thought for programmers on some of the strength and limitations of those features that are built into Perl (thus leaving off discussion of mixins and traits).

Single inheritance avoids problems with confusing inheritance issues (e.g., which class did that method come from?) and some obscure composition issues which are detailed more carefully in the original traits paper. It's simple to use, but it leads to problems with code duplication. For example, you have a Mammal class and you want to instantiate a Tiger, which just happens to be a subclass from Mammal. All is well and good. However, you're a deviant terrorist version of Siegfried and Roy and you want to create an exploding tiger. In this case, you have a Bomb class and you think your Tiger isa Bomb in addition to a Tiger. Unfortunately, you programming language doesn't support multiple inheritance so you just cut and paste the code. Now you have code duplication. Bad.

One method that Java uses to get around this is the interface. If a class implements an interface, then that class must implement the methods described in that interface. You could create a Bomb interface and that would ensure that all classes that implement bomb would have an explode method, but that wouldn't solve the code reuse problem.

With multiple inheritance, like we have in Perl, you can state this:

package Tiger; use base qw(Mammal Bomb);

Now let's say that mammal inherits from LifeForm, so your class heirarchy looks like this:

           LifeForm
               |
            Mammal  Bomb
               \     /
                Tiger

This looks fine, but what you, the deviant Siegfried and Roy didn't know, is that the creator of LifeForm took into account stories of spontaneous human combustion and build an explode method directly into the LifeForm class. What happens? When you call $tiger->explode, &Tiger::explode is not found, but this is OO programming! The @ISA tree is searched leftmost, depth first. Thus, Perl looks for &Mammal::explode and doesn't find it. Then it looks for &LifeForm::explode, finds it and calls it. Bomb::explode is never called. Unfortunately for you, this is a rather weak explosion that simply consumes your Tiger in fire. Most disappointing.

You can get around this by rearranging the order of classes in your use base ... statement, but what if Bomb now implements a method that you need from Mammal? You have the same problem. Yet another way of dealing with this problem is to have this method:

sub Tiger::explode { my $self = shift; $self->Bomb::explode; }

That works, but now we've encoded implementation information directly into our class and made it less flexible. This directly contravenes some of the benefits that we expect from OO programming.

Delegation, on the other hand, neatly avoids all of these problems. In your Tiger constructor, let's assume that you have a _bomb slot. This slot will contain a bomb object. Later, when you wish to explode, you do this:

sub new { my ($class, $data) = @_; # deliberately simplistic! $data->{_bomb} = Bomb->new; bless $data, $class; } sub Tiger::explode { my $self = shift; $self->{_bomb}->explode; }

With this, we explicitly call the method we want, as in the $self->Bomb::explode call above, but we limit the knowledge of which class we are using in one spot (in this case, in the constructor).

On the surface, this might not seem like much of a win, but what happens if this is more complicated and we have four methods we need from our Bomb class?

sub set_timer { my $self = shift; $self->Bomb::set_timer; } sub light_fuse { my $self = shift; $self->Bomb::light_fuse; } sub explode { my $self = shift; $self->Bomb::explode; } sub disarm { my $self = shift; $self->Bomb::disarm; }

Now we have a problem. Without delegation, if we gained a conscience and wanted to change the Bomb to a Toy::Bomb, we have multiple places that we would need to change the class name. With delegation, we only have one place to change it and the delegated methods don't change.

Another benefit of delegation is that you are relying only on the published implementation. With inheritance, you must comply explicitly with the inherited implementation and (particularly with Perl), if private methods change, you might accidentally step on them. You are also more likely to be forced to use the parent class implementation. If the parent class uses a blessed hashref, you will use a blessed hashref whether you want to or not. Delegation decouples the implementation from the Bomb and Mammal implementation, with the exception of the implied contract that "this interface will not change". The major drawback of delegation, though, is that it can often take more work to set up. You can't inherit the methods that you want, so you have to explicitly create them.

As any competent manager will tell you, delegation is the preferred method for dealing with exploding tigers.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Re: Object Terminology (and an awful shaggy dog story)
by stvn (Monsignor) on Jan 12, 2004 at 17:53 UTC
    Ovid,

    Exploding Tigers. Nice, although you might get some slack from PETA for it. :)

    While I agree with all your points (for the most part). I still love my multiple-inheritance. Eiffel provides a handy way to handle some of those name conflicts you speak of, by allowing you to rename methods (among other things). Now Eiffel forces you to adjust your methods to the renamed method. In perl though, this is not nessecary. I submit the following code:

    # assume that these 2 methods are # defined in some module and then # exported into your classes's # package namespace. sub redefines ($$$) { no strict 'refs'; no warnings 'redefine'; my ($package, $old_name, $new_name) = @_; my ($caller_package) = caller(); $caller_package->isa($package) || die "Can't rename from a package you do not inherit from"; my $old_method = $package->can($old_name); (defined($old_method)) || die "you cannot rename a method you dont have"; my $current_method; if (exists ${"${caller_package}::"}{$old_name}) { $current_method = \&{"${caller_package}::$old_name"}; } else { $current_method = sub { die "Method no implemented" } } *{"${caller_package}::$old_name"} = sub { my $self = shift; my ($_caller_package) = caller(); return $self->$new_name(@_) if ($package->isa($_caller_package)); # or $self->$current_method(@_); }; *{"${caller_package}::$new_name"} = $old_method; } sub next_method { my ($self, $package, @args) = @_; my ($p, $f, $l, $function) = caller(1); $self->isa($package) || die "Can only dispatch on ancestors of $p"; my @module_path = split /\:\:/, $function; my $caller_function = $module_path[-1]; my $dispatch = $package->can($caller_function); return $self->$dispatch(@args); }
    Your Exploding Tiger can then be implemented as such.
    use strict; use warnings; package ExplodingTiger; ## import the 2 functions above somehow @ExplodingTiger::ISA = qw(Tiger Bomb); redefines("Tiger", "explode", "spontaneouslyCombust"); sub new { my ($class) = @_; my $exploding_tiger = { bomb_type => "Bomb" }; return bless($exploding_tiger, ref($class) || $class); } sub explode { my ($self, @args) = @_; print "ROAR!!! I'm an exploding Tiger!\n"; $self->next_method($self->{bomb_type}, @args); } 1;

    See my scratchpad for a complete runnable example.

    Your delegation solution neglected to allow for the original spontaneous combustion behavior to still remain around. It gets redefined in ExplodingTiger::explode. Also, you are faced with the problem that if code from your inherited Tiger class uses the explode method anywhere for a specific purpose it will dispatch to the ExplodingTiger::explode method instead, and the behavior of the Bomb::explode method it delegates too may not be appropriate. The above solution will account for that (see the scratchpad example for the details). It also allows for you to inherit other Bomb methods without needing to delegate.

    Another benefit of delegation is that you are relying only on the published implementation. With inheritance, you must comply explicitly with the inherited implementation ...
    I am not 100% sure i understand what you are saying here. I assume you are saying that you must deal with the fact a class is inherited, and that is part of the implementation? If that is not what you are saying then disregard the rant that follows, if it is what you are saying, then ...

    I believe this is not an issue of inheritance as a concept, but in the implementation of various documentation tools (javadoc, I curse thee!!!).

    In Eiffel, there are 2 "views" of a class available in the documentation. A view of the methods implemented by the class directly, and a "flattened" view of all the methods available from the class (all implemented and all inherited).

    I believe this takes the OO idea of encapsulation to its next level. In encapsulating inheritance information as well. I mean after all you are supposed to encapsulate/hide the implementation in OOP, and is not inheritance part of a classes implementation?

    -stvn