perlbot - Bag'o Object Tricks (the BOT)
ここにあるインスタンス変数の使用であるとか、オブジェクトやクラス 関係の機構といったトリックやヒントのコレクションは、好奇心を刺激 するようなものです。読者は、オブジェクト指向の定義や方法論につい ての議論については適切な教科書を参照することが求められます。つま り、これはオブジェクト指向プログラミングのチュートリアルを指向し たものでなく、Perlのオブジェクト指向機能についての包括的なガイド でもなく、はたまたスタイルガイドとなるべきものでもありません。
Perlのモットーはまだ生きています: やり方は一通りじゃない。
$selfの型を確かめることはしないこと。$selfの型が正当であってもそ のパッケージがあなたの期待しているものと異なっているとき、クラス が継承されていればおかしな結果となってしまいます。規則 5を参照し てください。
object-oriented (OO, オブジェクト指向) あるいは indirect-object (IO, 間接オブジェクト)の構文が使われたならば、そのオブジェクトは おそらくは正しい型であり、それについて偏執的になる必要はありませ ん。Perlはいかなる場合においても偏執的言語(paranoid language)で はありません。OO構文やIO構文をsbuvertしている(くつがえす、滅ぼす) 人達がいたなら、そういった人々はおそらく自分たちが行っていること を知っているでしょうし、あなたは彼らにそうさせておくべきなのです。 規則 1を参照してください。
引数二つのbless()を使いましょう。コンストラクターを使ってサブク ラスにしましょう。コンストラクターを継承するを参照のこと
サブクラスは、すぐ上のスーパークラスが何であるかを知ることが許さ れています。スーパークラスはサブクラスについての何かを知ることを 許されていません。
やたらと継承を使わないようにしましょう。“using”(使用), “containing”(包含), “delegation”(委譲)といった関係()は、ほとんどの場合継承よりも適 切なものとなります。オブジェクトの関係, USING RELATIONSHIP WITH SDBM, 委譲 を参照してください。
オブジェクトとは名前空間です。オブジェクトを通して、パッケージを 大域的にアクセスできるようにします。これはシンボルのホームパッケ ージに関する当て推量(guess work)を取り除くでしょう。 クラスコンテキストとオブジェクトを参照してください。
IO構文はそれほどややこしいものではありません。しかしその反面、見 つけるのが難しいバグを引き起こすあいまいさ(ambiguities)になる傾 向があります。あなたが好まないとしても、人々はOO構文を使うことが 許されているのです。
メソッドを関数呼び出し形式で使わないようにしましょう。さもなけれ ばあなたはいつの日か痛い目にあうこととなるでしょう。誰かがそのよ うなメソッドをスーパークラスに移動させればあなたのプログラムは 壊れてしまうのです。規則 2の偏執狂になることに繋がりかねません。
自分がメソッドのホームパッケージを知っていると仮定しないでくださ い。それをやってしまうと、他の人がそのメソッドをオーバーライドす るのが難しくなってしまいます。コードの再利用を考える を参照し てください。
インスタンス変数
無名配列や無名ハッシュはインスタンス変数を保持するのに使うことが できます。名前付きのパラメーターと一緒にお見せしましょう。
package Foo;
sub new {
my $type = shift;
my %params = @_;
my $self = {};
$self->{'High'} = $params{'High'};
$self->{'Low'} = $params{'Low'};
bless $self, $type;
}
package Bar;
sub new {
my $type = shift;
my %params = @_;
my $self = [];
$self->[0] = $params{'Left'};
$self->[1] = $params{'Right'};
bless $self, $type;
}
package main;
$a = Foo->new( 'High' => 42, 'Low' => 11 );
print "High=$a->{'High'}¥n";
print "Low=$a->{'Low'}¥n";
$b = Bar->new( 'Left' => 78, 'Right' => 40 );
print "Left=$b->[0]¥n";
print "Right=$b->[1]¥n";
スカラーインスタンス変数
無名のスカラーは、インスタンス変数が一つだけ必要とされるときのみ 使うことができます。
package Foo;
sub new {
my $type = shift;
my $self;
$self = shift;
bless ¥$self, $type;
}
package main;
$a = Foo->new( 42 );
print "a=$$a¥n";
インスタンス変数の継承
次の例は、どのようにして新しいクラスにスーパークラスからインスタ ンス変数を継承するのかということを示すものです。ここでは、スーパ ークラスのコンストラクターを呼び出すこととと新しいオブジェクトで インスタンス変数を一つ追加するということを行っています。
package Bar;
sub new {
my $type = shift;
my $self = {};
$self->{'buz'} = 42;
bless $self, $type;
}
package Foo;
@ISA = qw( Bar );
sub new {
my $type = shift;
my $self = Bar->new;
$self->{'biz'} = 11;
bless $self, $type;
}
package main;
$a = Foo->new;
print "buz = ", $a->{'buz'}, "¥n";
print "biz = ", $a->{'biz'}, "¥n";
オブジェクトの関係
以下の例では、“containing”や“using”といったオブジェクト間の 関係をどのように実装するかということを説明しています。
package Bar;
sub new {
my $type = shift;
my $self = {};
$self->{'buz'} = 42;
bless $self, $type;
}
package Foo;
sub new {
my $type = shift;
my $self = {};
$self->{'Bar'} = Bar->new;
$self->{'biz'} = 11;
bless $self, $type;
}
package main;
$a = Foo->new;
print "buz = ", $a->{'Bar'}->{'buz'}, "¥n";
print "biz = ", $a->{'biz'}, "¥n";
スーパークラスのメソッドをオーバーライドする
以下の例ではどのようにスーパークラスのメソッドをオーバーライドす るのか、そしてオーバーライドされたメソッドをどのように呼び出すの かを示します。擬似クラス(pseudo-class) SUPERはプログラマーが スーパークラスのメソッドをそのメソッドがどのクラスに属するかを知 らなくても呼べるようにします。
package Buz;
sub goo { print "here's the goo¥n" }
package Bar; @ISA = qw( Buz );
sub google { print "google here¥n" }
package Baz;
sub mumble { print "mumbling¥n" }
package Foo;
@ISA = qw( Bar Baz );
sub new {
my $type = shift;
bless [], $type;
}
sub grr { print "grumble¥n" }
sub goo {
my $self = shift;
$self->SUPER::goo();
}
sub mumble {
my $self = shift;
$self->SUPER::mumble();
}
sub google {
my $self = shift;
$self->SUPER::google();
}
package main;
$foo = Foo->new;
$foo->mumble;
$foo->grr;
$foo->goo;
$foo->google;
SDBMを使ったusing関係
この例はSDBMクラスに対するインターフェースを示します。この例では、 SDBMクラスと、新しいクラスMydbmとの間の“using”関係を作り出しま す。
package Mydbm;
require SDBM_File;
require Tie::Hash;
@ISA = qw( Tie::Hash );
sub TIEHASH {
my $type = shift;
my $ref = SDBM_File->new(@_);
bless {'dbm' => $ref}, $type;
}
sub FETCH {
my $self = shift;
my $ref = $self->{'dbm'};
$ref->FETCH(@_);
}
sub STORE {
my $self = shift;
if (defined $_[0]){
my $ref = $self->{'dbm'};
$ref->STORE(@_);
} else {
die "Cannot STORE an undefined key in Mydbm¥n";
}
}
package main;
use Fcntl qw( O_RDWR O_CREAT );
tie %foo, "Mydbm", "Sdbm", O_RDWR|O_CREAT, 0640;
$foo{'bar'} = 123;
print "foo-bar = $foo{'bar'}¥n";
tie %bar, "Mydbm", "Sdbm2", O_RDWR|O_CREAT, 0640;
$bar{'Cathy'} = 456;
print "bar-Cathy = $bar{'Cathy'}¥n";
コードの再利用を考える
オブジェクト指向言語の一つの強みとは、古いコードと新しいコードを 混ぜて使うのが簡単だと言うことです。以下の例では、まず最初にどの ようにコードの再利用を妨害するかということを、続いてどのようにコ ードの再利用を促進するかということを示します。
最初の例では、“プライベート”なメソッドBAZ()にアクセスするため にfully-qualifiedメソッド呼び出しを使っているクラスをお見せしま す。二番目の例ではオーバーライドすることのできないBAZ()メソッド をお見せします。
package FOO;
sub new {
my $type = shift;
bless {}, $type;
}
sub bar {
my $self = shift;
$self->FOO::private::BAZ;
}
package FOO::private;
sub BAZ {
print "in BAZ¥n";
}
package main;
$a = FOO->new;
$a->bar;
今度はメソッドBAZ()をオーバーライドしてみましょう。FOO::bar()で GOOP::BAZ()を呼び出したいのですが、これはFOO::bar()が陽に FOO::private::BAZ()を呼び出しているのでできません。
package FOO;
sub new {
my $type = shift;
bless {}, $type;
}
sub bar {
my $self = shift;
$self->FOO::private::BAZ;
}
package FOO::private;
sub BAZ {
print "in BAZ¥n";
}
package GOOP;
@ISA = qw( FOO );
sub new {
my $type = shift;
bless {}, $type;
}
sub BAZ {
print "in GOOP::BAZ¥n";
}
package main;
$a = GOOP->new;
$a->bar;
再利用可能なコードを作成するには、クラスFOOを修正しなければなら ず、クラス FOO:privateを平らにします。次の例では、FOO::BAZ()を使 っている場所でメソッドGOOP:BAZを置くことのできるクラスFOOをお見 せします。
package FOO;
sub new {
my $type = shift;
bless {}, $type;
}
sub bar {
my $self = shift;
$self->BAZ;
}
sub BAZ {
print "in BAZ¥n";
}
package GOOP;
@ISA = qw( FOO );
sub new {
my $type = shift;
bless {}, $type;
}
sub BAZ {
print "in GOOP::BAZ¥n";
}
package main;
$a = GOOP->new;
$a->bar;
クラスコンテキストとオブジェクト
パッケージとクラスコンテキストの問題を解決するためにオブジェクト を使いましょう。メソッドが必要とするすべてはオブジェクトを通して 使用可能であるべきであり、メソッドに対するパラメーターとして渡さ れるべきなのです。
あるクラスが、ときとしてメソッドから使うためにスタティックなデー タやグローバルなデータを持つことがあるでしょう。サブクラスでその ようなデータをオーバーライドして、それを新しいデータに置き換えた いことがあるかもしれません。このような事態になったとき、スーパー クラスは新しいデータのコピーをどうやって見つけるのかを知ることが できません。
この問題は、メソッドのコンテキストを定義するためのオブジェクトを 使うことによって解決することができます。オブジェクト中にあるメソ ッドにデータのリファレンスを見せましょう。もう一つの手段はメソッ ドにデータを探しまわさせる(“自分のクラスにあるの? それともサブ クラス? どのサブクラス?”)ことですが、これは不便ですし、ハッカー 的にすぎます。オブジェクトに、データがどこに位置するかを知らせる メソッドを持つようにさせるのが良いのです。
package Bar;
%fizzle = ( 'Password' => 'XYZZY' );
sub new {
my $type = shift;
my $self = {};
$self->{'fizzle'} = ¥%fizzle;
bless $self, $type;
}
sub enter {
my $self = shift;
# %Bar::fizzle と %Foo::fizzle のいずれを使うべき
# なのかを推測しようとしないこと。オブジェクトは既
# に答を知っています。だから、それを訊ねるだけです。
#
my $fizzle = $self->{'fizzle'};
print "The word is ", $fizzle->{'Password'}, "¥n";
}
package Foo;
@ISA = qw( Bar );
%fizzle = ( 'Password' => 'Rumple' );
sub new {
my $type = shift;
my $self = Bar->new;
$self->{'fizzle'} = ¥%fizzle;
bless $self, $type;
}
package main;
$a = Bar->new;
$b = Foo->new;
$a->enter;
$b->enter;
コンストラクターの継承
継承可能なコンストラクターは、特定のクラスに直接blessすることの できるbless()の第二形式を使うべきです。以下の例において、クラス FOOのコンストラクターを通じて構成されたオブジェクトであってもそ れはFOOではなく、BARとして存在するようになることに注意してくださ い。
package FOO;
sub new {
my $type = shift;
my $self = {};
bless $self, $type;
}
sub baz {
print "in FOO::baz()¥n";
}
package BAR;
@ISA = qw(FOO);
sub baz {
print "in BAR::baz()¥n";
}
package main;
$a = BAR->new;
$a->baz;
委譲(Delegation)
SDBM_Fileのようなクラスはforeignオブジェクトを生成するので、効率 良くサブクラス化することができません。そのようなクラスは“using” 関係のような先に言及したものや、委譲(delegation)のような集成テク ニックを用いることで拡張することができます。
以下の例では、メッセージフォワーディング(message-forwarding)を実
現するためにAUTOLOAD関数を使っている
delegation(委譲)をお見せし
ます。これは、MydbmオブジェクトがSDBM_Fileオブジェクトと全く同じ
ように振る舞うようにさせるものです。カスタマイズしたFETCHメソッ
ド()やSTORE()メソッドを追加することによって、今やMydbmクラスを拡
張することもできるのです。
package Mydbm;
require SDBM_File;
require Tie::Hash;
@ISA = qw(Tie::Hash);
sub TIEHASH {
my $type = shift;
my $ref = SDBM_File->new(@_);
bless {'delegate' => $ref};
}
sub AUTOLOAD {
my $self = shift;
# Perlインタープリターはメッセージの名前を
# $AUTOLOADと呼ばれる変数に置いています
# DESTROYメッセージは伝播させるべきでない
return if $AUTOLOAD =‾ /::DESTROY$/;
# パッケージ名を取り除く
$AUTOLOAD =‾ s/^Mydbm:://;
# delegateにメッセージを渡す
$self->{'delegate'}->$AUTOLOAD(@_);
}
package main;
use Fcntl qw( O_RDWR O_CREAT );
tie %foo, "Mydbm", "adbm", O_RDWR|O_CREAT, 0640;
$foo{'bar'} = 123;
print "foo-bar = $foo{'bar'}¥n";