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

I want to add an optional method to a set of similar classes. It's a hook for the classes to implement special behavior in a situation. Most will not use it.

My first thought was to make a base class with the method defined as a no-op, and make them all inherit from it. Then I thought I could do this by checking if the class has implemented this method with can() before calling it, and skip the otherwise pointless inheritance. This method is only called from one place, so it wouldn't result in can() being strewn around everywhere.

Still leaning toward the first way, but I'm curious which of these sounds better to other OO coders. Does the can() approach sound evil?

Replies are listed 'Best First'.
Re: OO design: abstract class or can()?
by chromatic (Archbishop) on Mar 23, 2007 at 05:23 UTC

    It sounds like a good use of Class::Trait to me. Inheriting an empty method is a little silly and checking can() can be homophonically ambiguous.

      So you're proposing something like:
      $o->some_method if $o->does( 'SomeTrait' )
      ?

        Just a quick comment.

        While does feels very similar to can in this case, it will future-proof things since does('SomeTrait') can indicate the ability to do many methods, while can('some_method') can only ever indicate one.

        -stvn

        Exactly.

        It's also a nice bit of future-proofing in case Perl ever gets multi-dispatch... and that day may be closer than many people think. (I mean dispatch based on role, not structural type.)

Re: OO design: abstract class or can()?
by Joost (Canon) on Mar 22, 2007 at 22:42 UTC
    I use can() when I don't have a base class and the method involved doesn't get called all that much.

    If I have a base class I'd just put a no-op method in there.

    Alternatively you can also demand that conforming classes provide the no-op method if they don't need that functionality (and possibly provide an "empty" base class for convenience). That way you don't have to inherit from the base class, you just need to have that method defined. Typing sub noop {} isn't that much work, after all.

Re: OO design: abstract class or can()?
by snoopy (Curate) on Mar 22, 2007 at 23:39 UTC
    A consideration is to preserve can() as a test for the applicability of a method.

    For example to avoid unnecessary or invalid processing around the method call:

    if ($myobj->can('do_something')) { my $input = do_something_before(); my $result = $myobj->do_something($input); do_something_after($result); }
Re: OO design: abstract class or can()?
by dragonchild (Archbishop) on Mar 23, 2007 at 04:16 UTC
    Put the no-op method in. That simplifies your client code so that it has to conform to a simpler API vs. having to know internals.

    Or, put it another way, what would you do to me if I published something to CPAN that required the can() crap?


    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: OO design: abstract class or can()?
by ikegami (Patriarch) on Mar 22, 2007 at 22:58 UTC

    I want to add an optional method to a set of similar classes.

    You could just import the method into the classes that need it.

    For example, if you created a module like the following,

    use strict; use warnings; package SpecialMethod; BEGIN { our @EXPORT = qw( special_method ); require Exporter; *import = \&Exporter::import; } use ... use ... sub special_method { ... } 1;

    then just say use SpecialMethod; from the various classes into which you want to import the method.

    Update: Oh wait. That doesn't address your main question at all. I don't think I can give a good answer without knowing more about what you are trying to accomplish, but it sounds like a perfectly reasonable use of can.

    Update: Added missing *import = line.

Re: OO design: abstract class or can()?
by shanna (Sexton) on Mar 23, 2007 at 00:44 UTC
    I like having a contract so I'd mix the method in through the magic of multiple inheritance.
    package Base; sub new { bless \my($scalar), shift } package Optional; use Carp qw(croak); sub optional_method { croak q{You said you would implement optional_method and didn't!}; } package A; use base qw(Base); # No optional_method. package B; use base qw(Base Optional); # Must implement optional_method. sub optional_method { 'Woot!' }
    You get ->can('optional_method') now but you also have your code croak if the contract isn't met. Or you can put shared code in the super Optional::optional_method. Your choice.
Re: OO design: abstract class or can()?
by gloryhack (Deacon) on Mar 23, 2007 at 01:31 UTC
    I frequently use can() as a means to extend behavior in just one or two isolated spaces, so it doesn't seem at all evil to me. If I find myself using can() too frequently, though, I take that as a sign that it's probably time to rethink the code.

    I prefer that my inheritance tree go from the most generic to the most application specific, so inheriting from a class whose method(s) will only be used while the wind is out of the south on leap day if it's Tuesday and the moon is new seems much closer to evil than can().

Re: OO design: abstract class or can()?
by eric256 (Parson) on Mar 22, 2007 at 23:34 UTC

    If you have a set of similar classes wouldn't that imply that they share more methods? Or at least have similar methods, seems to me that similar classes = similar methods = derived from a common class, even if that common class is very thin. It really depends though, if you look down the road and don't see them sharing anything else then inheritance might be over kill, but if there is a chance they will overlap more and more you would definitely want to go with inheritance.


    ___________
    Eric Hodges
Re: OO design: abstract class or can()?
by adrianh (Chancellor) on Mar 23, 2007 at 13:20 UTC
    Still leaning toward the first way, but I'm curious which of these sounds better to other OO coders. Does the can() approach sound evil?

    Not evil, but I'd still prefer the null method option myself. Removes a conditional (which makes the code simpler in my eyes), and gives an explicit sub in the base module to document.