Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

$functions xor $methods

by Ovid (Cardinal)
on Oct 30, 2002 at 00:09 UTC ( [id://208923]=perlmeditation: print w/replies, xml ) Need Help??

Recently, I was pointing out the difficulty with subclassing a particular module. The module appeared to be object-oriented and useful, but it lacked some functionality that I needed. The answer? Subclass it!

Um, no.

The module, Data::FormValidator, makes a mistake that I see all too frequently and that, frankly, I have been guilty of in the past. It appears to be object oriented, but it uses function calls internally, rather than method calls. While I pointed this out in the post I referred to, this problem seems common enough that I feel it deserves a root node.

sub foo { my ($self, $data) = @_; $self->{bar} = _some_function($data); }

That might look perfectly reasonable at first, but what happens if I need to subclass this method and I need _some_function()? I can no longer call _some_function() directly as my subclass will be in a different namespace. I can't do $self->_some_function() as I have now added $self as the first argument. Reimplementing _some_function() in my subclass means that I'm duplicating code or using it as a wrapper around an ugly construct like the following:

sub _some_function { my ( $self, $data ) = @_; return ParentClass::_some_function($data); }

Needless to say, the above eliminates many of the benefits of subclassing. The following code may illustrate the problem more clearly.

#!/usr/bin/perl -w use strict; use Data::Dumper; package Foo; sub new { my $class = shift; bless {}, $class; } sub foobared { my $self = shift; $self->{foo} = _test( 3 ); } sub _test { shift } package Bar; @Bar::ISA = 'Foo'; sub foobared { my $self = shift; $self->{foo} = $self->_test( 3 ); } package Main; my $o = Foo->new; $o->foobared; print $o->{foo},$/; my $o2 = Bar->new; $o2->foobared; print $o2->{foo};

That prints out something similar to the following:

3 Bar=HASH(0xa065cc8)

If you're going to use object oriented programming, use method calls. Don't use functions.

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: $functions xor $methods
by japhy (Canon) on Oct 30, 2002 at 00:40 UTC
    I made this mistake with some OO modules of mine, even those I released to CPAN, I think. The "problem" (that we have to live with) is that method calls are slower than function calls (since Perl needs to look through the inheritance tree to find what method gets called). It's just something you have to do.

    _____________________________________________________
    Jeff[japhy]Pinyan: Perl, regex, and perl hacker, who'd like a job (NYC-area)
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

      Couldn't you circumvent this "problem" by calling methods as functions inside the class definition? So instead of doing
      sub foo { my ($self, $data) = @_; $self->{bar} = $self->method($data); }
      why not do this:
      sub foo { my ($self, $data) = @_; $self->{bar} = method($self, $data); }
      Then the interface to the method will remain subclassable, but you don't have the performance hit of a method call. As long as this stays hidden inside the class, and you know the method you want to call is in the class and not a superclass, I don't see what the problem would be.

      kelan


      Perl6 Grammar Student

        Unfortunately, that's not subclassable. The method-as-function calls in the super class will always call the super class functions, even when a subclass overrides them.

        Makeshifts last the longest.

(tye)Re: $functions xor $methods
by tye (Sage) on Oct 30, 2002 at 15:06 UTC

    Don't subclass modules unless they were designed to be subclassed.

    [ In the following text I use "you" a lot. I'd use "one" instead but that is a style that I think many would find a bit strange. In other words, I'm using "you" to refer to you, the reader, not to Ovid in particular. ]

    Modules have two public interfaces. The first defines how you use the module and is a contract that, if you follow it, ensures that future versions of the module will still work with your code. This should be documented in the POD for that module.

    The second defines how you can customize the module. Most modules don't bother to define this. So I think you would be a fool to subclass most modules. The module designer probably has made no promise that the next release of the module won't switch to using arrays for member data instead of hashes. Or that they won't switch to having the main class of the module only containing class methods including a factory (called "new") that creates objects in a different class. Or any number of other reasonable design refinements for the internals that would break your subclass.

    It is perfectly reasonable to have utility functions in an OO module. If these aren't defined in the documentation on how to customize the module then you shouldn't be calling them directly at all because there is no reason to expect that the next version of the module won't change such things. So of course they are inconvenient to call; it is a clue to tell you "Stop doing that!".

    If you find a need to subclass such a module, then you really need to first patch the module to support subclassing (which requires getting buy-in from the module maintainer on "freezing" some features of the implementation). Of course, I wouldn't be surprised if you were hoping to use subclassing in no small part because you didn't want to patch the module.

    Now, if you are trying to write a module that supports subclassing and you have utility functions that you want to make conveniently available to subclasses... then you should probably just turn them into methods (that ignore their first argument).

            - tye
Re: $functions xor $methods
by adrianh (Chancellor) on Oct 30, 2002 at 11:08 UTC

    Surely the obvious "solution" is to just call the function as a function?

    package Bar; @Bar::ISA = 'Foo'; sub foobared { my $self = shift; $self->{foo} = Foo::_test( 3 ); }

    No code duplication. Works as expected. Functions are called as functions. Methods as methods.

    I sometimes use functions in this way - exactly because they are not inherited. It means you can encapsulate some of your class implemention details in subs and not have to worry about some sub-class overriding them and breaking the public interface.

    Not having looked at the code, I'm not sure if this applies to Data::FormValidator, but from your example it looks like you're trying to override an implementation detail (that leading "_" is a bit of a give away) in which case you're asking for trouble :-)


    Corrections:

    • 2:31pm 30 Oct 2002 GMT: added missing "your" to correct my poor grammar.
Re: $functions xor $methods
by particle (Vicar) on Oct 30, 2002 at 01:43 UTC

    i don't have time to do the legwork, but it might be possible to use some magic combination of AUTOLOAD and goto &NAME to call the parent class if the function doesn't exist in the subclass.

    hope that helps, Ovid

    ~Particle *accelerates*

      Only problem is that it requires a hardcoded 'parent' value (unless theres some way to return that?)
      sub AUTOLOAD { if(defined &{__PARENT__::$AUTOLOAD}) { goto &{__PARENT__::$AUTOLOAD} } }
      (note i left out the part about getting the right value in $AUTOLOAD, chopping out the package and stuff)
        something like this seems to work and doesn't require hardcoded names. it just grabs the first parent method (in @ISA order) it finds:
        sub AUTOLOAD { no strict; my $METHOD; ($METHOD = $AUTOLOAD) =~ s/.*:://; foreach my $PARENT (@ISA) { if(defined &{$PARENT.'::'.$METHOD}) { goto &{$PARENT.'::'.$METHOD} } } }
        either this, or use Damian's NEXT module.

        cheers,
        Aldo

        King of Laziness, Wizard of Impatience, Lord of Hubris

        SUPER::$AUTOLOAD should work.
Re: $functions xor $methods
by rir (Vicar) on Oct 30, 2002 at 14:08 UTC
    I understand your frustration but you are just wrong.

    Object oriented inheritance is not about reading the implementation of your SUPER classes.

    If you are overriding method you should expect to reimplement it.

    Consider: if the _helper_function's effect was contained in the method, you would not be complaining, but you would still have to reimplement the effect.

    The SUPER class author is telling you outright not to count on the interface of _helper_function.

    Just be happy he gave you an example implementation.

    If you're going to use object programming, use method calls. Don't use functions.

    Exactly right, but this should be applied to yourself as a client writer.

      Okay, I'll just fess up to now having doubts about what I wrote :) Clearly I shouldn't be worrying about an object's internals. As tye points out, subclassing modules that aren't designed to be subclassed is asking for trouble.

      The problem is, what do I do if I need a module's functionality but don't want to rewrite it? Looking at Data::FormValidator, for example, there are some features that I could use here at work, but make absolutely no sense to have in that module, so submitting a patch isn't right. Subclassing it also isn't an option because I'd be forced to rewrite virtually the entire module to support the features that I need -- and they still wouldn't be appropriate in that module (because they're tied very specifically to our business needs), so I'd still have to either subclass what I wrote or find a different method of solving my problem. In this case, that "different method" forced me to ignore a useful module that I wanted to use and rewrite it.

      Cheers,
      Ovid

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

        The problem is, what do I do if I need a module's functionality but don't want to rewrite it?

        Two possibilities spring to mind.

        1. Simply write a module which uses the other. This is probably the appropriate way to do it if you are adding functionality to a module anyway. Subclassing, I think, is best when you want to change the functionality, particularly by making it more specific.

        2. A tried and true method that doesn't get enough respect these days is to create a copy of the module and modify it. Forking a module's development is a perfectly fine form of code reuse which, like any other, has its advantages and its disadvantages.

        -sauoq
        "My two cents aren't worth a dime.";
        

        I am one of those few who have to watch for false hubris more than false laziness, so this is what I'd want to do:

        Patch the module to clean up the internals, and add hooks - whether that be subclassability, slots for user-supplied callbacks or whatever else may seem appropriate -, and of course submit the patch back to the author. Then I'd use the well-defined interface I just created to add my own, case-specific functionality inside my application.

        Hopefully the author will either accept the patch or be inspired - and so whoever next needs something like me will also benefit.

        Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://208923]
Approved by jynx
Front-paged by Aristotle
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (5)
As of 2024-04-19 22:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found