オーバーライドの影響を受けないメソッドの呼び出し方
いや、タイトルは大げさなんですが。
通常、そのクラスでしか使わないプライベートなメソッドを定義したいときに、Perlの慣習としてメソッド名の先頭に_をつけます。
package HogeA; use strict; use warnings; sub new { bless { str => 'hoge:' } , shift } sub _foo_method { my $self = shift; return $self->{str}.'foo'; } sub bar_method { my $self = shift; print $self->_foo_method; } my $obj = HogeA->new; $obj->bar_method; # hoge:foo
こんな感じですね。
_foo_methodメソッドはHogeAクラス内部でしか使わないわけですが、気をつけなければならないことがあります。
このHogeAを継承したクラスを作る際、たまたま_foo_methodがカブってしまった場合、
package HogeB; use strict; use warnings; use base qw/HogeA/; sub _foo_method { my $self = shift; return $self->{str}.'mogemoge'; } sub hei_method { my $self = shift; print $self->_foo_method; } my $obj = HogeB->new; $obj->hei_method; # hoge:mogemoge これはOK $obj->bar_method; # hoge:mogemoge これはまずい。hoge:fooと表示されてほしい。
_foo_methodメソッドはHogeBクラス内部でしか使わないつもりで作成したけども、たまたまHogeAでも同名のメソッドが定義されていた場合、bar_methodの動きが変わってしまうわけです。
どうすればよいか。
ひとつは僕の昔の記事(privateでfinalなメソッドの定義 - Unknown::Programming)で書いたように無名サブルーチンを使う方法が考えられます。
ただなんだか無理やりくさいし、先に宣言しとかないといけないしびみょー。
じゃどうするのかというと単純に関数呼び出しするだけだよーってこと。
package HogeA; use strict; use warnings; sub new { bless { str => 'hoge:' } , shift } sub _foo_method { my $self = shift; return $self->{str}.'foo'; } sub bar_method { my $self = shift; # 関数として呼ぶ print _foo_method($self); # HogeA::_foo_method($self)と同義 }
こうしとけばHogeBで_foo_methodをオーバーライドしちゃってもモウマンタイだよね。
package HogeB; use strict; use warnings; use base qw/HogeA/; sub _foo_method { my $self = shift; return $self->{str}.'mogemoge'; } sub hei_method { my $self = shift; print _foo_method($self); } my $obj = HogeB->new; $obj->hei_method; # hoge:mogemoge $obj->bar_method; # hoge:foo
さてここまで読んで何当たり前のこと言ってんだテメーとか聞こえてきそうですが、その通りですorz。
ことのついでにもうひとつ当たり前のことを書きますが、$selfを引き継いだまま関数として呼び出す際に下記のようにも書けます。
sub bar_method { my $self = shift; print $self->HogeA::_foo_method(); # HogeA::_foo_method($self);と同義 } sub hei_method { my $self = shift; print $self->HogeB::_foo_method(); # HogeB::_foo_method($self);と同義 }
って感じです!ちゃんちゃん。
・・・で終わりではなく、ここでふと思ったのが、自身のクラスでしか使わないメソッドだとわかっているのにわざわざ「HogeA::」とか「HogeB::」とかを書かなきゃいけないのが面倒だなあと思ったわけです。
特にパッケージ名がとっても長い場合、
$self->Hoge::Muge::Foo::Bar::Baz::Class::_foo_method();
みたいな感じになるのでかなり面倒です。
# じゃあ関数呼び出ししろよという外野の声が聞こえてきそうですが無視無視^^
そこで自パッケージのメソッドを関数呼び出ししてくれるNEXTモジュールにヒントを得てfuncというモジュールをでっちあげてみました。
package func; use strict; use warnings; use Carp qw/croak/; our $AUTOLOAD; sub AUTOLOAD { my ($proto) = @_; my $caller_class = caller; my $wanted = $AUTOLOAD; my ($wanted_class, $wanted_method) = $wanted =~ m{(.*)::(.*)}g; my $method = qq{${caller_class}::$wanted_method}; croak qq{Can't locate object method "$wanted_method" via package "$caller_class"} unless exists &$method; goto &$method; } 1; package HogeA; use strict; use warnings; use func; sub new { bless { str => 'hoge:' } , shift } sub _foo_method { my $self = shift; return $self->{str}.'foo'; } sub bar_method { my $self = shift; print $self->func::_foo_method; # こんな感じで呼ぶ }
funcっていう名前はどうなの?っていうのと、たかだか自パッケージのメソッドを呼び出すだけのためにAUTOLOADという高いコストを払う必要性がいったいどこにあるんだと小三時間くらい問い詰められそうなのですが、もし処理速度が速いならこーゆー考え方も有りかなぁと思ったわけです。
実際は激遅なんで使いもんにならないわけですがw