Beefy Boxes and Bandwidth Generously Provided by pair Networks DiBona
"be consistent"
 
PerlMonks  

AbstractClass

by tilly (Archbishop)
on Dec 01, 2000 at 08:53 UTC ( #44300=sourcecode: print w/ replies, xml ) Need Help??

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.

Comment on AbstractClass
Download Code
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")
Re: AbstractClass
by Anonymous Monk on Dec 07, 2000 at 01:48 UTC
    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 class ".
                            "$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).
    
      Oh, that was me.
        No, it was me.

Back to Code Catacombs

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: sourcecode [id://44300]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (4)
As of 2014-04-19 14:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (481 votes), past polls