Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Abstract class methods

by tilly (Archbishop)
on Dec 01, 2000 at 04:19 UTC ( [id://44268]=note: print w/replies, xml ) Need Help??


in reply to Interfaces in Perl?

This just hit me as The Right Way To Do This.
package Foo; use Carp; # Time passes # Import method that checks existence of abstract methods in subclasse +s. sub import { my $pkg = shift; return if $pkg eq __PACKAGE__; foreach my $meth ( qw(foo bar) ) { $pkg->can($meth) or croak("Class $pkg does not define method $meth +"); } $pkg->SUPER::import(@_); }
The import method will do nothing if you import the base class, but will die a horrible flaming death if you ever use or import a class that inherits from it which does not implement the abstract methods you want.

Note that the base class cannot (with this method) define the abstract methods itself, it just lists them. (They could be in an array, etc.)

UPDATE
I added inheritance so one abstract class can inherit from another. This breaks on multiple inheritance because Perl's SUPER mechanism doesn't handle this cleanly.

UPDATE 2
tye pointed out in the chatter that I should document the fact that if you define your own import method and don't call the SUPER::import method inside of it, then you will break this mechanism.

UPDATE 3
I took the snippet above and turned it into a useful module which may be found at AbstractClass.

Replies are listed 'Best First'.
Re: Abstract class methods
by Dominus (Parson) on Dec 01, 2000 at 04:40 UTC
    As Randal pointed out, this won't work if the abstract class itself implements stub routines. But it also fails for a bigger reason: At the time you do the can test, the subroutine whose existence you are trying to check has not yet been compiled!

    The solution I suggested in the other thread fixes this problem by deferring the check until after compilation is complete. My sample code doesn't deal properly with inherited methods, however. I think a hybrid approach might be effective.

    You would use the INIT block approach that I showed, and then in the INIT block, use ->can, and check to see if the returns subroutine was equal to the stub:

    sub INIT { ... my $ref = $inheriting_class->can($method); if (! defined($ref)|| defined(&$method) && $ref == \&$method) { $bad = 1; warn ...; } }
    What is this doing? It tries to resolve the method with $can, the way Ben shows. If there is no such method, that's bad. If there is a method, it then checks to see if there's a stub routine in the abstract class itself, and, if so, if the subclass's method is actually this stub; if so, that's bad too.

    You still have the problem with abstract classes that inherit from other abstract classes, of course, but I think solving these problems should be just a SMOP.

      Mark's (Dominus) solution in the other thread is just what I was looking for. I want all classes where I "use Interface;" to implement and not inherit the methods listed in the Interface package.

      The particular design problem I need this for is one in which I need each class, regardless of what base class they inherit from, to implement themselves a group of methods.

      Java provides a built in facility for doing this, the "implements" keyword, which works almost exactly as the "use Abstract;" Mark proposes. (To it I would suggest adding the word "Interface" to the abstract class name, to achieve Java-like clarity through convention). Thus his example would look like this:

      package DuckInterface; use Carp; my @inheritors; sub import { my $caller = caller; push @inheritors, $caller; } my @abstract_methods = qw(swim fly); sub INIT { my $bad = 0; for my $class (@inheritors) { for my $meth (@abstract_methods) { no strict 'refs'; unless (defined &{"${class}::$meth"}) { $bad=1; warn "Class $class should implement DuckInterface, but does +not define $meth.\n"; } } } croak "Compilation aborted" if $bad; } 1;
      And a class that "implements" DuckInterface:
      package RedDuck; use DuckInterface; sub swim { "I swim like a red duck"; } sub fly { "I fly like a red duck, and DuckInterface guarantees I do!"; }
        There is no need to have an INIT function. Just have the import function skip the abstract class, and then have it do the check. Works exactly like my example did.
      Please try it and attempt to produce a case of breakage.

      In my tests it works perfectly.

      In theory it should work fine as well.

      If A is an abstract class and B inherits from it then the check that B has implemented the methods does not happen when B calls import on A since A ignores the method call with itself as the class. It only happens when A handles the call to import B into whatever C wanted to use it, at which point B has been compiled and loaded.

        tilly says:
        > In my tests it works perfectly.
        Oh, I see! Yes, that's really clever.
Re (tilly) 1 (tests): Abstract class methods
by tilly (Archbishop) on Dec 01, 2000 at 06:00 UTC
    Given that two respected Perl gurus both thought that this would break, here is a working test with three modules. Foo and Bar are abstract. Baz must implement their abstract methods.

    Here is Foo.pm:

    package Foo; use Carp; # Import method that checks existence of abstract methods in subclasse +s. sub import { my $pkg = shift; return if $pkg eq __PACKAGE__; foreach my $meth ( qw(foo) ) { $pkg->can($meth) or croak("Class $pkg does not define method $meth +"); } $pkg->SUPER::import(@_); } 1;
    Here is Bar.pm:
    package Bar; use Carp; use Foo; @ISA = qw(Foo); # Import method that checks existence of abstract methods in subclasse +s. sub import { my $pkg = shift; return if $pkg eq __PACKAGE__; foreach my $meth ( qw(bar) ) { $pkg->can($meth) or croak("Class $pkg does not define method $meth +"); } $pkg->SUPER::import(@_); } 1;
    Here is Baz.pm:
    package Baz; use Bar; @ISA = qw(Bar); sub foo {print "This is method foo\n";} sub bar {print "This is method bar\n";} 1;
    And then here is the test script:
    use Baz;
    If you comment out either sub in Baz.pm, the program will die immediately.
Re: Abstract class methods
by merlyn (Sage) on Dec 01, 2000 at 04:27 UTC
    The can will return true if the base class implements stub die subroutines, as the original poster proposed, however. Maybe what's needed is a direct defined check: something like:
    die unless defined *{__PACKAGE__."::$_"}{CODE} for qw(foo bar);
    I'm not sure but this might require softrefs enabled.

    -- Randal L. Schwartz, Perl hacker

      That is why I said that the technique fails if the base class implements stubs. It must list, not define, the methods.

      The direct defined check will work, but it means that every subclass must implement all of the methods. So if A is an abstract class, and B inherits from A and C inherits from B, then by default C has to define every one of the required methods.

Re: Abstract class methods
by Anonymous Monk on Feb 04, 2005 at 09:48 UTC
    I tried to use this with a hybrid module containing a class implementation and few functions (legacy code) exported using Exporter, and after few days struggling found two problems:

    1) This code will not call SUPER::import when it's imported by itself because of the first return.
    2) Exporter uses caller() to populate the namespace, which is broken by this.

    My modified code is here:

    sub import { my $pkg = shift; if ($pkg ne __PACKAGE__) { foreach my $meth ( qw(foo bar) ) { $pkg->can($meth) or croak("Class $pkg does not define meth +od $meth"); } } my $is_exp = $pkg->isa('Exporter'); $Exporter::ExportLevel++ if $is_exp; $pkg->SUPER::import(@_); $Exporter::ExportLevel-- if $is_exp; }

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (7)
As of 2024-03-28 11:14 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found