In Perl, what is the right way for a subclass to alias a method in the base class?
I simply hate how CGI::Application's accessor for the CGI
object is called query
.
I would like my instance classes to be able to use an accessor named cgi
to get the CGI
object associated with the current instance of my CGI::Applicat开发者_Python百科ion
subclass.
Here is a self-contained example of what I am doing:
package My::Hello;
sub hello {
my $self =shift;
print "Hello @_\n";
}
package My::Merhaba;
use base 'My::Hello';
sub merhaba {
goto sub { shift->hello(@_) };
}
package main;
My::Merhaba->merhaba('StackOverflow');
This is working as I think it should and I cannot see any problems (say, if I wanted to inherit from My::Merhaba
: Subclasses need not know anything about merhaba
).
Would it have been better/more correct to write
sub merhaba {
my $self = shift;
return $self->hello(@_);
}
What are the advantages/disadvantages of using goto &NAME
for the purpose of aliasing a method name? Is there a better way?
Note: If you have an urge to respond with goto
is evil don't do it because this use of Perl's goto
is different than what you have in mind.
Your approach with goto
is the right one, because it will ensure that caller
/ wantarray
and the like keep working properly.
I would setup the new method like this:
sub merhaba {
if (my $method = eval {$_[0]->can('hello')}) {
goto &$method
} else {
# error code here
}
}
Or if you don't want to use inheritance, you can add the new method to the existing package from your calling code:
*My::Hello::merhaba = \&My::Hello::hello;
# or you can use = My::Hello->can('hello');
then you can call:
My::Hello->merhaba('StackOverflow');
and get the desired result.
Either way would work, the inheritance route is more maintainable, but adding the method to the existing package would result in faster method calls.
Edit:
As pointed out in the comments, there are a few cases were the glob assignment will run afoul with inheritance, so if in doubt, use the first method (creating a new method in a sub package).
Michael Carman suggested combining both techniques into a self redefining function:
sub merhaba {
if (my $method = eval { $_[0]->can('hello') }) {
no warnings 'redefine';
*merhaba = $method;
goto &merhaba;
}
die "Can't make 'merhaba' an alias for 'hello'";
}
You can alias the subroutines by manipulating the symbol table:
*My::Merhaba::merhaba = \&My::Hello::hello;
Some examples can be found here.
I'm not sure what the right way is, but Adam Kennedy uses your second method (i.e. without goto
) in Method::Alias (click here to go directly to the source code).
This is sort of a combination of Quick-n-Dirty with a modicum of indirection using UNIVERSAL::can
.
package My::Merhaba;
use base 'My::Hello';
# ...
*merhaba = __PACKAGE__->can( 'hello' );
And you'll have a sub called "merhaba" in this package that aliases My::Hello::hello
. You are simply saying that whatever this package would otherwise do under the name hello
it can do under the name merhaba
.
However, this is insufficient in the possibility that some code decorator might change the sub that *My::Hello::hello{CODE}
points to. In that case, Method::Alias
might be the appropriate way to specify a method, as molecules suggests.
However, if it is a rather well-controlled library where you control both the parent and child categories, then the method above is slimmmer.
精彩评论