http://qs321.pair.com?node_id=253720

Probably the only thing that annoys me about Perl5's object system is doing inheiratence, especially abstract classes. To those not familer with OO-jargon, an abstract class is where the methods of a class are defined but not implemented. A subclass is required to implement each method in its abstract parent class. This has been discusssed previously in Interfaces in Perl?, but I'd like to revisit the issue.

For instance, we might have an Animal class that defines methods like move(), eat(), and drink(). However, animals have a huge number of different ways to move around, eat, and to drink. Some might move via fins, others with legs, and some with wings. Our Animal class would only define that all animals must have some means of moving around, eating, and drinking without specifiying how these tasks are accomplished.

IMHO, there are two requirements for an object system to do abstract classes well:

  1. A subclass must implement all the methods of its abstract parent
  2. Each method must be called in the same way

I hold little hope for doing #2 in Perl5. It requires a way to define what types of variables are allowed to be passed in. While Apocolypse 6 defines ways to do this in Perl6, that won't be of help to us in the near future. While Perl5 has a prototyping system, it isn't meant to be used for this task and is of little benifit. The only means to accomplish #2 is to have a community agreement to implement subclassed methods in a certain way. Embedding it in the language avoids mistakes and increases the code's self-documentation.

#1 might still be possible. This means either doing compile-time enforcement of abstract classes, or checking the existance of each method at run-time and dieing if a subclass doesn't implement it. Intuitively, compile-time enforcement is more efficent.

Some solutions out there right now:

Define abstract methods to die() on being called

This is the most common method I've seen. Its main problem is that a method which was never implemented in a subclass will still work as long as that method is never called. Theoretically, a mis-implemented subclass could be used for years if a little-used method is never called.

Use Attribute::Abstract

Its syntax is nicer than the die(), but is implemented internally the same way, and thus has the same problem of little-used methods.

Use Class::Virtual

Requires authors of subclasses to die() on their own if they don't implement everything. Consistant with Perl's "allow everything so that the programmer can decide how to do things". Something of a tradeoff of laziness in favor of flexibility.

Use Class::Virtually::Abstract

Does what Class::Virtual does, but with compile-time inforcement. This eliminates the problem of subclass authors having to die() on their own. However, its implemented via import(), so if any of your subclasses defines its own import(), the compile-time checking goes away. This puts limits on how subclasses can be implemented, even though a principal point of an abstract class is that we don't care how subclasses are implemented, provided methods x, y, and z exist.

Update: As dws and Elgon point out, I used the definition of "Abstract Class" incorrectly. "Interface" is probably the right term for what I'm looking for. In any case, I suspect the means to implement both of these in Perl would be very similar.

----
I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
-- Schemer

Note: All code is untested, unless otherwise stated

Replies are listed 'Best First'.
Re: Abstract Classes
by chromatic (Archbishop) on Apr 28, 2003 at 15:50 UTC
    a principal point of an abstract class is that we don't care how subclasses are implemented, provided methods x, y, and z exist

    Sure you do; you're forcing them to inherit from an abstract base class and to declare the existence of their methods at compile-time. That's two implementation details that your approach enforces.

    Whether that's good or bad, you tell me. :)

    One feature that could save a lot of cut and pasted code is support for mixins in the descendant classes. If there's no inheritance of implementation, there really ought to be another way to reuse similar code.

      Good, because without specifying the methods to implement, why have an abstract class?

      My point was that inheriting and checking the existance and signatures (if possible, which doesn't appear to be the case in Perl5) should be the only things abstract classes force on subclasses. Sorry if I wasn't clear.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

Re: Abstract Classes
by dpuu (Chaplain) on Apr 28, 2003 at 18:20 UTC
    The purpose of an abstract class is to constrain the implementor of its subclasses. This being perl, we don't try too hard to prevent circumvention of our constraints. So a very simple implementation of an abstract class is:
    package BaseClass; sub new { my ($class, @args) = @_; my $self = bless {}, $class; $self->init(@args); $self->check_interface__BaseClass; $self; } sub init { die "oops, this is the base class" } sub check_interface__BaseClass { my ($self) = @_; foreach my $method (qw[ foo bar baz ]) { $self->can($method) or die "method $method not implemented"; } }
    With this simple approach, if your subclasses don't redefine "new" (they define the init method instead), then they get the interface checking. If they do redefine "new", then they can still check the interface by calling the check_interface method explicitly.

    Sure, it has its limitations, but if you want a lightweight solution, this seems to fit the bill. --Dave

Re: Abstract Classes
by dws (Chancellor) on Apr 28, 2003 at 20:43 UTC
    To those not familer with OO-jargon, an abstract class is where the methods of a class are defined but not implemented.

    That's not the generally agreed-on definition.

    As a source on this (a source I have within reach), I'll cite the "Gang of Four" Design Patterns book, page 15:

    An abstract class is one whose main purpose is to define a common interface for its subclasses. An abstract class will defer some or all of its implementation to operations defined in subclasses; hence an abstract class cannot be instantiated.

    That's the definition I've been used to working with for the past decade or so.

    You might be thinking of an "interface".

      dws,

      This is exactly as I had it explained to me by those nice people at Sun when I did a Java course, what hardburn is describing sounds like an interface, as you say.

      • Abstract classes may or may not contain implementation for each of their methods and they may not be instantiated. There is no coercion of methods which the subclass must contain as any which are not implemented are inherited.

      • Interfaces are merely contracts which list the methods which an implementing class MUST have. They may not be instantiated either, but then again they aren't really classes and have no implementation so this should not be surprising

      The utility of these two when used together correctly is that an Abstract Class provides common behaviours which subclasses will inherit, whereas an Interface merely defines methods which any extending class must implement. In the first case neanderthals and humans both have the same drink() method inherited from the hominids abstract class, which they extend. In the second case, both humans and fish MUST be able to breathe as they both implement the breathers interface but the actual mechanisms used are different.

      Elgon

      If this node offends you, re-read it and reconsider. Think for a bit. I am almost certainly not trying to offend you, although I am frequently irreverent or flippant. Rememer - Please never take anything I do or say seriously.

Re: Abstract Classes
by hding (Chaplain) on Apr 28, 2003 at 18:33 UTC
    With the disclaimer that I haven't taken time to read things over very carefully, how does your first point interact with the desire someone might have to make an abstract subclass of an abstract class? In this case you wouldn't necessarily want to force all methods to be implemented in that class.