継承と委譲の選択。
見ての通りはてな初心者なので毎日いろんな機能を試して喜んでたら、なにやらidトラックバックとやらが!なにげにblog系の物を使ったのが初めてなので地味にうれしかったり。笑
で、見てみたらShibuya.pmでICFPへのPerlでの参加結果を発表しておられたY.Sawaさんからd:id:succeed:20051113のような指摘だった。うーむ。正直なところ夏前にはオブジェクト指向って何?って感じだったので、まだいまいちわかってない部分が多い。JavaやC++なんかを実務として触ったことがないってのも原因なんだろうけど。
って事で委譲ですよ、奥さん。まずそもそも委譲ってのがなんなのか。英語で言うとdelegateらしい。なんでも出来るProxyソフトが確かそんな名前だったなぁってのは置いとくとして。Classの中に必要とするClassのインスタンスを取得して保持しておき、実際の処理はそのインスタンス経由でオリジナルのClassに任せる感じらしい。
って事で、d:id:hideden:20051109を委譲で書き直してみるとしよう。
package CGI_JP_conv; use CGI; use strict; use Jcode; sub new { my $class = shift; my $cgi = new CGI(@_); return bless { cgi => $cgi }, $class; } sub get_cgi_query { return $_[0]->{'cgi'}; } sub set_code { my $self = shift; my $code = shift; my $jcode = Jcode->new(); $self->{'conv'} = sub { return @_; }; if ( $code eq 'sjis' ){ $self->{conv} = sub { return $jcode->set(@_)->sjis; }; } elsif ( $code eq 'euc' ){ $self->{conv} = sub { return $jcode->set(@_)->euc; }; } elsif ( $code eq 'jis' ){ $self->{conv} = sub { return $jcode->set(@_)->jis; }; } elsif ( $code eq 'utf8' ){ $self->{conv} = sub { return $jcode->set(@_)->utf8; }; } } sub param { my $self = shift; return $self->{'cgi'}->param(@_) unless( defined $self->{'conv'} ); my @value = map { &{$self->{'conv'}}($_) } $self->{'cgi'}->param(@_); return wantarray ? @value : $value[0]; } 1;
多分こんな感じ?間違ってたらスンマセン。使う場合は
#!/usr/bin/perl use lib './lib'; use strict; use CGI_JP_conv; my $q = new CGI_JP_conv; $q->set_code('utf8'); my $cgi_q = $q->get_cgi_query; print $cgi_q->header; print $q->param('test');
って感じかな。確かにこうすると、CGI.pmに対する改変は一切なくなるため、意図しないメソッド名の衝突によるオーバーライド等は無くなる。11月08日の技術勉強会にも出てきてたが、アンダーバーで始まっているメソッドは上書きしない方が良いとかいう慣例は実際として守られてない例も多いし、言語的には守らなくても特にエラーはでない。確かにClass::DBIで_croakメソッドを上書きしてthrowするようにするとかは普通にやってる。(まぁコレの場合はPODにそうやれって書いてあるんだけど。)
委譲の場合、必要とするメソッドを全て書く(実際は保持してるインスタンスへ丸投げして、返ってきた値をreturnするだけだが)か、保持してるインスタンス自身を返すメソッドを書くしかない気がする。この場合、CGI.pmは馬鹿でかいし、汎用性が高いモジュールな為、必要とするメソッドだけを抽出するというのが難しいのでインスタンスを返すメソッドを作ってみた。
さて、ここで一つ考えなければならない事が。
my %params = $q->Vars;
の場合をどうするか。d:id:hideden:20051109にも書いたが、Varsというメソッドはtieされたハッシュを返し、実際に取得する処理はAUTOLOADによりFETCHというメソッドが呼ばれた場合にのみその都度paramを呼び出して取得している。ここでどうやって実装するか。
sub Vars { my $self = shift; my %vars = $self->{'cgi'}->Vars; my $jcode = Jcode->new(); return %vars unless( defined $self->{'conv'} ); $vars{$_} = &{$self->{'conv'}}($vars{$_}) foreach keys %vars; return %vars; }
っとまぁ、コレでも動くだろう。ただ、せっかくtieしてその都度呼び出されてたのが、結局一括で取得してハッシュに普通に格納することになる。ま、別にそれでもいいっちゃいいけど。笑
実はコレの元ネタはかなり前に書いたとあるサイト用のスクリプトなのだが、Varsで取得したハッシュをClass::DBIのcreateやsetしてupdateってのに使っている。うん、言いたいことはわかる。とても良くない設計だろう。ちょっとした作業用の物なので、いわゆる手抜きだ。笑
その関係上、formのnameはDBのカラム名とあわせてあったりするのだが、mode=update等の制御用のカラム名には存在しない引数なんてのもある。そのままVarsで取得するとそんなカラムねーよ!!って怒られるので、
my $q = new CGI_JP_conv; my $mode = $q->paramalp('mode'); $q->delete('mode'); my %data = $q->Vars; DBHogehoge::hogetable->create(\%data);
のような事を色々している。まったくもって困った設計だ。笑
さてこの場合、どうするか。先ほどのVarsの実装方法だと、一度ハッシュに格納された物はもう明示的にいじろうとしない限り不変だ。本来のCGI.pmの場合はtieを使用しているためハッシュをいじらなくてもCGI.pm経由でした操作が勝手に反映される。やっぱりなんかこっちの方が便利だ。って事は同様の操作感を実現するためには、
- tie用にTIEHASH,DESTROY,FETCH,STORE,DELETE,EXISTS,FIRSTKEY,NEXTKEYなどを作る
- delete,append等のメソッドも委譲するように書く
って事が必要なのだろう。うーん。えーっと・・・ほら・・その・・・・なんていうか・・・・・めんどくせぇ!!!!!!! あーあ、言っちゃった。笑
ま、最初は面倒だけど後から再利用する云々を考えたら結果的に楽だろ?ってのがオブジェクト指向の思想なような気がするので、そもそもこんな事を言ってる時点で失格なのかもしれないが、そこまで色々考えるなら普通に継承しちゃった方が(好ましくないのだろうが)楽だ。
こういう場合ってどうすれば良いんだろう。そもそも設計が根本的にわりぃよ!!って意見も含めて誰か教えてくれないかなー。
ってことで、コメントとか色々お待ちしてます!ちゃんと一回言われたら覚えるので!