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.
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:??; | [reply] |
|
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 | [reply] [d/l] [select] |
|
| [reply] |
|
(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
| [reply] |
Re: $functions xor $methods
by adrianh (Chancellor) on Oct 30, 2002 at 11:08 UTC
|
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.
| [reply] [d/l] |
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*
| [reply] [d/l] [select] |
|
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) | [reply] [d/l] |
|
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 | [reply] [d/l] |
|
SUPER::$AUTOLOAD should work.
| [reply] |
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. | [reply] [d/l] [select] |
|
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.
| [reply] |
|
-sauoq
"My two cents aren't worth a dime.";
| [reply] |
|
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.
| [reply] |
|
|