kyle has asked for the wisdom of the Perl Monks concerning the following question:

At $work, someone suggested that, if you have a class that you want not to be subclassed, ever, bless into the package directly like so:

sub new { bless {}, __PACKAGE__ }

I said that this will fail silently for the subclass, and I'd rather have an obvious explosion:

sub new { my $class = shift; if ( $class ne __PACKAGE__ ) { die "Class $class attempts to inherit from final class, ' . __P +ACKAGE__; } # ... }

Now that I look at it, though, a subclass need only override new, and it's home free either way. So I'm wondering, is there a way to make a class that some other module cannot use as a base class?

I figure it's enough just to die with an obvious and clear message about what was intended. If another programmer wants to violate that, then it's understood that the warranty is void. As acceptable as that is, it would be even better if the failure could happen at compile time (i.e., when one tries to use the subclass) so we don't even have to wait for the first instantiation to cause an explosion.

Update: Just to be clear (because I realize now that I have not been), I do not advocate this practice. I'm not asking for a better way to do this because I think it's a good idea to do it. I'm asking out of curiosity because I couldn't think of a good way to do it, and I thought it was an interesting problem. I suspect that doing this to a class rarely, if ever, serves a good purpose.

Replies are listed 'Best First'.
Re: A class that cannot be subclassed.
by merlyn (Sage) on Feb 27, 2008 at 23:55 UTC
    At $work, someone suggested that, if you have a class that you want not to be subclassed
    ... you're insane.

    At least, at my work, that would be the first reaction.

    Why be hostile to subclassing?

      Our coding standards say to prefer composition over subclassing. There's a brief summary of some of the arguments at Consider composition instead of subclassing. I don't see that as a reason to be hostile to the practice to the point of taking steps to prevent it, but this might not have been a general situation either. I suppose there could be a class that the author knows would break if subclassed. In that case, it might make sense to try to force that not to happen.

        The original node made me think of "I need a class that can't be used on Tuesdays. I could just check localtime in new(), but somebody could intentionally set their computer's clock wrong..."

        I suppose there could be a class that the author knows would break if subclassed.

        If that were the case, then I would put the checking at/around the point that I thought would break if it was subclassed. I wouldn't go the indirect route and try to prevent all forms of subclassing. And this would also avoid the obvious and wise questioning of "why do you want to make subclassing fatal?". I would also question a conclusion so broad as "this will break if subclassed". I expect such would more correctly be stated as "this will likely break if subclassed in the obvious way".

        But that is based on a lot of speculation. It would be nice to know why such a bizarre (IMHO) requirement was formulated.

        - tye        

        Our coding standards say to prefer composition over subclassing.

        You have coding standards, so do you have code reviews or some sort of collective code ownership?

        "Our coding standards say to prefer composition over subclassing."

        That's fine. But do it with documentation, not code.

Re: A class that cannot be subclassed.
by Joost (Canon) on Feb 27, 2008 at 21:45 UTC
    I can't think of a way you can do this.

    You can make a class that would be useless to subclass. Just put all instance data in lexicals (i.e. Inside-Out) and check the ref() of $self at each method invocation.

    In any case, having a class that can't be subclassed is completely useless by itself. As far as I can see the only reason it's even possible to define such classes in some languages is that under certain circumstances the compiler/runtime can optimize calls - it doesn't have to support (as much) polymorphism.

Re: A class that cannot be subclassed.
by dragonchild (Archbishop) on Feb 28, 2008 at 03:58 UTC
    No matter what, I can always decorate your object. Something like:
    package Decorator; sub new { my ($class, $obj) = @_; bless \$obj, $class; } sub parent { ${$_[0]} } sub AUTOLOAD { our $AUTOLOAD =~ s/([^:])+$/$1/; my $self = shift; $self->parent->$AUTOLOAD( @_ ); }

    At that point, I can add whatever I want and instead of $self->SUPER::method( @args ), I can just do $self->parent->method( @args );. This "trick" works in every single language and works cross-languages, too. And there's absolutely no way to test for it. None. If I wanted to add child-class data, I could either inside-out or make a hashref or whatever. The possibilities are endless.


    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?

      Well, that *almost* completely works. Where it can fail is if the parent is in a class hierarchy itself, and that framework uses methods from the entire hierarchy. You can't override those. For example:

      package GP; sub foo { my $self = shift; # ... $self->bar(); # ... } sub bar { my $self = shift # but I can be overridden } package P; sub bar { my $self = shift; # stuff. }
      Assuming P could prevent subclassing (I'm not sure how, and I still fail to see why, but that's not relevant here), any attempt for your Decorator to override bar when calling $obj->foo() is doomed to failure.

      Problem #2 (though this isn't quite as problematic in Perl as it is in other languages - I only bring it up because you say your decorator works "in every single language"): checking the type of the object. If I were to pass around an object of type Decorator, perl allows this to work as long as no one checks what Decorator *is*. e.g., code that checks $obj->isa("P") will fail. Now, granted, you can override isa. Of course, then there are people who advocate reversing this, and calling UNIVERSAL::isa($obj, "P") (does that count as a way to test for it?), and you can't stop that as far as I know. Update: of course you can, this IS perl, after all... so I have to assume there's a way to get ref $decorator_object to return "P", too...

      Even perl6 introduces some problems here - if I already have a function somewhere that takes a parameter of type "P", an object of type "Decorator" won't suffice. Other languages, such as C++ or Java, already have this: if you create a decorator generic, say Decorator<T>, and try to pass that around as if it were of type T, it won't really work. Now, in all these languages, you probably can override an implicit conversion of Decorator to the parent type (where you return $self->parent()), but you'll again lose all over your method overrides.

        Of course, then there are people who advocate reversing this, and calling UNIVERSAL::isa($obj, "P") (does that count as a way to test for it?), and you can't stop that as far as I know.

        Sure you can. UNIVERSAL::isa does just that to prevent people from breaking things like Test::MockObject.

Re: A class that cannot be subclassed.
by dynamo (Chaplain) on Feb 27, 2008 at 22:15 UTC
    Well, as far as making it impossible to subclass, no, I don't think that's doable. However making things difficult is easy. (isn't it always?) Suppose you have a method selfcheck, that checks the species of itself before running each other method (and at destruction):
    package Foo::Bar; my @lineage = ('Mom', 'Dad'); @ISA = (@lineage); sub selfcheck { die "You can\'t change who I am." unless __PACKAGE__ eq 'Foo::Bar'; map { die "You can\'t change who my parents are." unless $lineage[$_] eq + $ISA[$_] } (0..(scalar @ISA - 1)); } sub DESTROY { $self->Foo::Bar::selfcheck; } sub method_name { my $self=shift; $self->Foo::Bar::selfcheck; # method stuff here } sub method_name2 { $self->Foo::Bar::selfcheck; # method stuff here }
    Now, you can overload all methods in the child class, but if you do you aren't really subclassing. You might as well subclass an anonymous hash. If you don't cover all bases, then the first time an inherited method is called, the program will die uglily and point out to the foolish programmer the error of his ways.

    - d

      I think the initial test in selfcheck should be:

      sub selfcheck { my $self = shift; die "..." if ref $self ne __PACKAGE__; # lineage check... }

      As you have it, the test would always pass because __PACKAGE__ is the package that the sub was compiled in (Foo::Bar) regardless of anything else (see perlmod).

      If I were a "malicious subclasser" faced with this, I could redefine selfcheck (I think), and then it's smooth sailing. What you could do is put that in a lexical subref, then it would stay private.

      package Foo::Bar; # ... my $selfcheck = sub { my $self = shift; die ... }; sub method { my $self = shift; $selfcheck->( $self ); # ... }

      I'm not sure where to go from there. Putting that check seems like a lot of work for the goal, but I guess it could be worse.