perlbot - Bag'o Object Tricks (the BOT)
ここにあるインスタンス変数の使用であるとか、オブジェクトやクラス関係の 機構といったトリックやヒントのコレクションは、好奇心を刺激するようなものです。 読者は、オブジェクト指向の定義や方法論についての議論については 適切な教科書を参照することが求められます。 つまり、これはオブジェクト指向プログラミングのチュートリアルを 指向したものでなく、Perl のオブジェクト指向機能についての 包括的なガイドでもなく、はたまたスタイルガイドとなるべきものでもありません。
Perlのモットーはまだ生きています: やり方は一通りじゃない。
$self の型を確かめることはしないこと。 $self の型が正当であってもそのパッケージがあなたの期待しているものと 異なっているとき、クラスが継承されていればおかしな結果となってしまいます。 規則 5 を参照してください。
オブジェクト指向 (OO) あるいは 間接オブジェクト (IO) 構文が使われたならば、 そのオブジェクトはおそらくは正しい型であり、 それについて偏執的になる必要はありません。 どうせ Perl は偏執的言語ではありません。 OO 構文や IO 構文をくつがえす人々がいたなら、そういった人々はおそらく 自分たちが行っていることを知っているでしょうし、 あなたは彼らにそうさせておくべきなのです。 規則 1 を参照してください。
引数 2 つの bless() を使いましょう。
コンストラクタを使ってサブクラスにしましょう。
コンストラクタの継承 を参照してください。
サブクラスは、すぐ上のスーパークラスが何であるかを知ることが許されています。 スーパークラスはサブクラスについての何かを知ることを許されていません。
やたらと継承を使わないようにしましょう。 実装("using")、包含("containing")、委任("delegation")といった 関係(少なくともある種の集合)は、たいていの場合継承よりも 適切なものとなります。 オブジェクトの関係, SDBM での実装関係, 委任 を参照してください。
オブジェクトとは名前空間です。 オブジェクトを通して、パッケージを大域的にアクセスできるようにします。 これはシンボルのホームパッケージに関する当て推量を取り除くでしょう。 クラスのコンテキストとオブジェクト を参照してください。
IO 構文はそれほどややこしいものではありません。 しかしその反面、見つけるのが難しいバグを引き起こすあいまいさになる 傾向があります。 あなたが好まないとしても、人々は 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";
以下の例ではどのようにスーパークラスのメソッドをオーバーライドするのか、 そしてオーバーライドされたメソッドをどのように呼び出すのかを示します。 擬似クラス 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;
SUPER はカレントパッケージのスーパークラス(Foo) を参照するのであって、
$self のスーパークラスではないことに注意してください。
この例は 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() にアクセスするために
完全修飾メソッド呼び出しを使っているクラスをお見せします。
二番目の例ではオーバーライドすることのできない 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;
SDBM_File のようなクラスは外部オブジェクトを生成するので、効率良く サブクラス化することができません。 そのようなクラスは実装("using")関係のような先に言及したものや、 委任のような集成テクニックを用いることで拡張することができます。
以下の例では、メッセージフォワーディングを実現するために
AUTOLOAD 関数を使っている委任をお見せします。
これは、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";