Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

checking a method is not subclassed

by water (Chaplain)
on Feb 02, 2006 at 15:26 UTC ( #527346=perlquestion: print w/ replies, xml ) Need Help??
water has asked for the wisdom of the Perl Monks concerning the following question:

Hi --

What would be the appropriate code to put in class Baz to throw an error if method Baz::foo was subclassed? And should such code go in a BEGIN block?

Thanks!

water

Comment on checking a method is not subclassed
Re: checking a method is not subclassed
by dragonchild (Archbishop) on Feb 02, 2006 at 15:33 UTC
    Oh, boy. The short answer is "You can't". If you want a "method" that cannot be overridden, you need to use a subroutine reference and, then, it's essentially private (which means you cannot call it from the outside).

    The long answer is:


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: checking a method is not subclassed
by brian_d_foy (Abbot) on Feb 02, 2006 at 15:42 UTC

    You can inspect the value you get from ref($self). If that's not Baz, some other package is calling the method through inheritance (or some other wacky thing). From there, decide what you want to do.

    --
    brian d foy <brian@stonehenge.com>
    Subscribe to The Perl Review

      For example,

      package Baz; sub new { my ($class) = @_; return bless({}, $class); } sub example { my ($self) = @_; if (ref($self) ne __PACKAGE__) { require Carp; Carp::croak( "Method &" . __PACKAGE__ . "::example must not be inherited" ); } print(ref($self) . " ok\n"); } package Baz::Foo; @ISA = 'Baz'; package main; Baz ->new()->example(); # ok. Baz::Foo->new()->example(); # dies.

      outputs

      Baz ok Method &Baz::example must not be inherited at !.pl line 28

      It's possible to circumvent this by re-blessing the object.

Re: checking a method is not subclassed
by gam3 (Curate) on Feb 02, 2006 at 16:13 UTC
    I think that the code below will do what you want. You do need to make sure that new is called.
    { package Baz; sub new { my $class = shift; if ($class ne 'Baz') { if ($class->can('foo') != Baz->can('foo')) { die "Don't do that $class"; } } bless {}, $class; } sub foo { 'foo'; } sub boo { 'foo'; } } { package Foo; our @ISA = 'Baz'; sub foo { 'Foo'; } sub boo { 'Foo'; } } { package Boo; our @ISA = 'Foo'; sub boo { 'Boo'; } } { package Goo; our @ISA = 'Baz'; sub boo { 'Boo'; } } my ($a, $b, $c); eval { $a = new Baz; }; warn $@ if $@; eval { $c = new Foo; }; warn $@ if $@; eval { $b = new Boo; }; warn $@ if $@; eval { $b = new Goo; }; warn $@ if $@;
    If you just want to make sure that Baz::foo is being called from code you control you can just do:
    $class->Baz::foo();
    I can't think of anything that you could do in a BEGIN block that would help with this problem.
    -- gam3
    A picture is worth a thousand words, but takes 200K.
Re: checking a method is not subclassed (in new)
by tye (Cardinal) on Feb 02, 2006 at 16:14 UTC
    sub Baz::new { ... croak ... if $class->can("foo") != \&Baz::foo; ... }

    ?

    I doubt I would encourage such tactics.

    - tye        

Re: checking a method is not subclassed
by salva (Monsignor) on Feb 02, 2006 at 16:36 UTC
    the usual way to do that on perl is:
    =item foo this method can not be redefined in derived classes =cut
Re: checking a method is not subclassed
by dragonchild (Archbishop) on Feb 02, 2006 at 16:51 UTC
    I've thinking about this more and I think you're missing something:
    package Baz; sub foo { # Do something here ... } package Baz::Ok; use base 'Baz'; sub foo { my $self = shift; # Call parent method first! my $rv = $self->SUPER::foo( @_ ); # Log the call somehow return $rv; }
    From the outside world, I didn't override foo(). I just decorated it a little. You might want to think about that.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: checking a method is not subclassed
by water (Chaplain) on Feb 02, 2006 at 17:04 UTC
    I'm missing something here, or I wasn't clear. Here's some code:
    # untested package Foo; sub bleep {...} sub baz { ... } package NewFoo; use base qw(Foo); sub baz { ... } package main; my $a = Foo->new; my $b= NewFoo->new; $a->bleep; $b->bleep; $a->baz; $b->baz;
    In this example, I am cool with a user calling bleep on either 'a' or 'b'. I'm also cool with a user calling baz on either 'a' or 'b'. What I'd like to detect from Foo is that a subclass (NewFoo) overrode a certain method. That is, $b->baz calls NewFoo::Baz, as that overrode Foo::Baz.

    I'm looking for a way for Foo to detect and carp that a subclass took away its rights to own the code for method baz.

    Probably this isn't "nice" OO -- "thou shalt never subclass me, dammnit" isnt friendly -- but I'm wondering how to do it anyway.

    I really like the suggestion about just saying "never subclass baz" in the docs (++ to that post), but need something stronger.

    The comments above about checking the ref on self don't seem relevant to me, as here the problem is that Foo::baz never gets involved when someone calls baz on $b... it is too late, the baz is then a NewFoo baz at that point.....?

    water

      if you really want to do it, you can use a CHECK block to browse all the symbol tables once all the modules and the script have been loaded and parsed, examining with packages subclass your special classes and that they don't redefine the methods you want to protect.

      You can even use a subrutine attribute to do it (as :final).

      Though modules could still work around that defining new methods at runtime via eval or just playing with the symbol tables.

        This sounds interesting. Yeah, I know others can use eval and play games with the symbol table at runtime. Leaving out those may-I-call-them pathological cases, could you say more about your approach?
      You are not clear where or when the carp should happen.
      The code that tye and I gave you would die on the my $b=NewFoo->new;.

      Do you want a function like Foo::overriden('foo') that would return the name of classes that overrode method foo()?

      -- gam3
      A picture is worth a thousand words, but takes 200K.
Re: checking a method is not subclassed
by water (Chaplain) on Feb 02, 2006 at 17:41 UTC
Re: checking a method is not subclassed
by nothingmuch (Priest) on Feb 02, 2006 at 19:44 UTC
    use Devel::Sub::Which qw/:universal/; INIT { # after all modules have been loaded. Will break with mod_perl my $which = __PACKAGE__->which("foo"); ( my $class = $which ) =~ s/::[^:]+$//; die "Subclassing 'foo' is not allowed" unless $class eq __PACKAGE_ +_; }
    Technicalities aside - are you sure you really want to check this? I can't imagine a scenario where this would be a good thing to do.

    -nuffin
    zz zZ Z Z #!perl
Re: checking a method is not subclassed
by acid06 (Friar) on Feb 06, 2006 at 01:48 UTC
    Attribute::Final does just what you want (I agree that the module is poorly named).
    Then you'll just have to declare your method with the :final attribute.

    This nomenclature comes from Java final methods.


    acid06
    perl -e "print pack('h*', 16369646), scalar reverse $="
Re: checking a method is not subclassed
by DrHyde (Prior) on Feb 06, 2006 at 10:05 UTC
    Methods can't be subclasses, only classes can be. Assuming that you mean 'inherited' instead, use Class::CanBeA to figure out what the subclasses of Baz are, then use exists() and can() to figure out what methods are being inherited in those subclasses and which are defined.

    You need to be careful though - perl supports multiple inheritance, so you may need to figure out your subclasses and then figure out all of their parent classes to find all the other roots of the inheritance tree. And if you do have multiple inheritance, I can't think of any way of telling that this subroutine came from that superclass and that this other subroutine came from that other superclass.

    package Foo; sub foo { 'foo' } package Bar; # Bar inherits 'foo' from Foo @ISA = qw(Foo); sub bar { 'bar' } # Bar defines 'bar' package main; use Class::CanBeA; my @subclasses = @{Class::CanBeA::subclasses('Foo')}; foreach my $class (@subclasses) { print "$class is a subclass of Foo\n"; foreach my $sub (qw(foo bar)) { if(!exists(&{$class.'::'.$sub}) && $class->can($sub)) { print $class.'::'.$sub." is inherited\n"; } } }

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://527346]
Approved by wfsp
Front-paged by grinder
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (7)
As of 2014-07-12 02:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    When choosing user names for websites, I prefer to use:








    Results (238 votes), past polls