http://qs1969.pair.com?node_id=44300
Category: Miscellaneous
Author/Contact Info
Description: This grew out of Interfaces in Perl?. This module provides a mechanism for creating what are called abstract base classes - classes that do not themselves define certain methods but impose an automatic check that they have been properly defined in derived classes.

Here is an example where Foo and Bar are two abstract base classes and Baz inherits from them. This was (not surprisingly) my test example. First Foo.pm, which requires a method called foo:

package Foo; use AbstractClass; @ABSTRACT_METHODS = qw(foo); sub foo {print "Subclasses are not allowed to use me\n";} 1;
Now Bar which is another abstract class inheriting from Foo, implementing foo, and requiring bar. I have shown the other way to declare an abstract method:
package Bar; use AbstractClass qw(bar); use Foo; @ISA = qw(Foo); sub foo {print "I am an allowable foo\n";} 1;
And finally, Baz.pm, a derived class inheriting from Bar:
use Bar; @ISA = qw(Bar); #sub foo {print "This is method foo\n";} sub bar {print "This is method bar\n";} 1;

UPDATE
Taking into account comments from Dominus (in a post which I think did not really deserve deletion) I have changed the name to AbstractClass, edited the documentation, and allowed abstract classes that do not define any new methods of their own. (This to allow classes that merely override a few methods.) I did not attempt to override bless, and won't pending some discussion. On the whole I don't much like overriding as a concept, and I am not sure that the gain is worth the pain.

Oh, and I added a version number. :-)

UPDATE 2
After some thought and an email to Damian I decided to implement a restriction to make it harder to create objects in an abstract class. While it would not be hard to make the check far stronger, doing so involves overriding bless everywhere, which is something I don't like philosophically. Also it would slow down the creation of objects from every constructor slightly. (As opposed to just slowing down constructors in abstract classes.)

UPDATE 3
Improved the check that subclasses overrode methods. Now you can usefully have an abstract class that inherits from a normal class and lists methods in that class which will need to be overridden (presumably because you have changed the implementation in some basic way). I am not sure that anyone would want to do so, but this issue was bugging me.

package AbstractClass;
$VERSION = 0.12;
use Carp;

sub bless ($@) {
  my $class = $_[1] || caller();
  if (exists $Registered{$class}) {
    confess("Cannot bless objects into abstract class $class");
  }
  else {
    CORE::bless(shift, $class);
  }
}

$text = '
#line 1 "(AbstractClass loaded for MYPACK)"
  package MYPACK;

  sub import {
    my $pkg = shift;
    return if $pkg eq "MYPACK";
    foreach my $meth (@ABSTRACT_METHODS) {
      my $does = $pkg->can($meth);
      if (not defined($does)) {
        Carp::croak("Class $pkg must define $meth for class MYPACK");
      }
      else {
        my $override = MYPACK->can($meth);
        if (defined ($override) and $does == $override) {
          Carp::confess("Class $pkg cannot inherit $meth from MYPACK")
+;
        }
      }
    }
    $pkg->SUPER::import(@_);
  }
  1;
';

sub import {
  shift; # Don't need my class name
  my $base = caller();
  $Registered{$base}++;
  *{"$base\::bless"} = *bless;
  @{"$base\::ABSTRACT_METHODS"} = @_;
  my $eval_str = $text;
  $eval_str =~ s/MYPACK/$base/g;
  eval($eval_str) or confess("Cannot execute:\n$eval_str\n\nError $@")
+;
}

1;

__END__

=head1 NAME

AbstractClass - Makes classes into abstract classes.

=head1 SYNOPSIS

In module SomeAbstractClass.pm:

  package SomeAbstractClass;
  use AbstractClass;
  @ABSTRACT_METHODS = qw(foo bar);

or more compactly:

  package SomeAbstractClass;
  use AbstractClass qw(foo bar);

=head1 DESCRIPTION

An abstract class is a class which defines methods
that must be overridden in classes that wish to inherit
from it which are not themselves abstract classes.
Normally the abstract class will then provide methods
whose implementations need these methods to exist.  
Whether or not it is an abstract class, it would crash
at some point if the methods were missing.  By moving
the check to when you first load the class, you can find
problems immediately and get better reporting of the
requirement.

It is customary to not have any objects in an abstract
class.  This module provides some enforcement of this
rule by overriding bless in abstract classes.

=head1 BUGS

Abstract classes are implemented with special import
functions.  Therefore this does not play well with
Exporter.  Also due to unfortunate restrictions in the
implementation of Perl's SUPER pseudo-package, multiple
inheritance will not play well with abstract classes.  If
you wish to use the capabilities of abstract classes and
multiple inheritance, you may wish to look at the more
intrusive Class::Contract.

This mechanism also means that if you define multiple
classes in a single file then you must manually import
each derived class to test it.  When each class is in its
own module, this happens automatically when you use the
module.

It is not hard to get around the rule that objects cannot
belong to an abstract class.  To fix that would require
overriding the core bless method, which brings up issues
of its own.

=head1 AUTHOR

AbstractBase was written by Ben Tilly <ben_tilly@hotmail.com>.
This module may be copied and modified on the same terms as
Perl itself.
Replies are listed 'Best First'.
Re: AbstractClass
by merlyn (Sage) on Dec 02, 2000 at 07:51 UTC

      If Schwern was listening, then he wasn't paying very close attention. His module requires each subclass to call a check function directly. Not as nice as many of the methods already posted here.

              - tye (but my friends call me "Tye")
        No, I wasn't listening, this is my first time really looking at PerlMonks. I was in the middle of mucking about with Tie::Cache::LRU and found myself writing a virtual base class and all these enforcement methods and such. So rather than make a one-off (heavens, no!) I slapped together Class::Virtual.

        So first off, Class::Virtual *doesn't* require each subclass to call a check function directly (although re-reading the documentation, I can see why one would think that. Will correct.) The check methods are there for code auditing purposes, reporting, internal use and subclasses of Class::Virtual (as we'll see in a moment). I'll change the docs to de-emphisize them and emphisize the real purpose of the module.

        The major difference I can see between Class::Virtual and AbstractClass is C::V does its work at run-time whereas AC works at compile-time. C::V waits until you actually call an unimplemented virtual method before it yells. This might seem silly (since it would have blown up anyway) but it does provide a more informative error. Also, doing the checks at compile-time would cause half my modules to blow up (being the poster boy for method auto-generation that I am). However, not to be outdone...

        package Class::Virtually::Abstract; use base qw(Class::Virtual); use Carp::Assert; assert( prototype('CORE::bless') eq '$;$' ); sub bless ($;$) { my $class = $_[1] || caller; if( grep { $_ eq 'Class::Virtually::Abstract' } @{$class.'::ISA'} +) { confess("Connot bless objects into abstract class $class"); } else { CORE::bless(shift, $class); } } sub import { shift; my $base = caller(); *{$base.'::bless'} = \&bless; *{$base.'::import'} = sub { my $class = shift; return if $class eq $base; foreach my $missing_meth ($class->missing_methods) { require Carp; Carp::croak("Class $class must define $missing_meth for cl +ass ". "$base"); } $class->SUPER::import(@_); }; 1; }
        That should emulate the compile-time behavior of AbstractClass and its mucking with bless(). (BTW Your prototype is wrong). And feel free to steal the closure trick to generate the import() instead of the eval(). I can flesh this out an ship it with Class::Virtual if you'd like.

        So, digging into the internals... a few things bother me about AbstractClass. First, it hijacks import(). Why should an OO module need an import() routine? Well, AbstractClass needs one. Lots of hybrid OO/functional modules need it (Class::Fields for one). Its too important to take away.

        Another problem with the import() route is its brittleness. Consider the following...

        package Foo; require Some::Abstract::Class; @ISA = qw(Some::Abstract::Class);
        Ooops. And this is a perfect valid and common, way of subclassing which defeats AbstractClass. Consider this, too...
        package Foo; use base qw(Some::Abstract::Class);
        base.pm does not call import() (and rightly so). AbstractClass defeated.

        Another, @ABSTRACT_METHODS bothers me. Magical global variables bother me, doubly so in OO contexts. Triply so when you start putting this magical global into other packages. Maybe if you put it into @{$base.'::__ABSTRACT_METHODS'} or something. I take the class data route instead.

        Overriding bless... I haven't decided if that's Evil or not. I'd rather define a default new() method that does the same thing. Besides, even if you do manage to generate an object from a virtual class, its going to explode the first time you use it.

        Another, with AbstractClass, SomeAbstractClass->can("some_virtual_method") returns false. With Class::Virtual it returns true (but if you try to use the method returned it will explode). I guess this is a design decision.

        I don't totally understand why AbstractClass doesn't work with MI, but Class::Virtual doesn't have any problem (god forbid, I can't live without MI).

        Finally, in the Class::Virtual scheme, virtual base classes are subclasses of Class::Virtual. I like this because of its synchronicity and because it allows the behavior of Class::Virtual to be altered (as for Class::Virtually::Abstract).

Re: AbstractBase
by Dominus (Parson) on Dec 01, 2000 at 18:59 UTC
    (Scrubbed pending deletion.)
      I didn't quite rule out AbstractBird, but I did put what is in hindsight a silly restriction on it. So my module is pretty close to what you would want.

      As I mentioned in the documentation, an abstract class that inherits from an abstract class will bypass the checks that abstract classes get. Why? Because when it uses the classes further up, they do not trigger their own check, and when someone calls it, it blocks its own check and does not rethrow to the classes that would have implemented the check.

      However I required abstract classes to implement at least one abstract method because I didn't think it was worthwhile to not do so. But you have given a good reason. I am about to update the name to AbstractClass. When I have done that your example would look like this. First your abstract base class:

      package SwimmingFlyingThing; use AbstractClass qw(swim fly);
      Now your abstract class that inherits:
      pacakage AbstractBird; use SwimmingFlyingThing; use AbstractClass; @ISA = 'SimmingFlyingThing'; sub fly { print "Flap Flap Flap" }
      And then finally a real class:
      package Duck; @ISA = 'AbstractBird'; sub swim { print "Paddle paddle paddle" } sub new { ... }
      And yes, Class::Contract does indeed implement a similar idea, but is a far bigger change to Perl's inheritance mechanisms.

      I am toying with the idea of making abstract classes unable to bless. With my module it would be trivial to do so. Just override bless in derived modules. This time I will be the one to write to Damian to get his opinion... :-)