perltooc - トムによる Perl のクラスデータのためのオブジェクト指向チュートリアル
オブジェクトクラスを宣言するとき、共通の状態をクラスの全てのオブジェクトが 共有したい状況に直面することがあります。 そのような クラス属性 は、いくぶん、クラス全体のグローバル変数のように はたらきます。 ですが、プログラム全体のグローバル変数とは違い、クラス属性は、クラス それ自身にとってのみグローバルです。
クラス属性が役に立つ例がいくつかあります。
生成したオブジェクトの数を保っておくために。 また、まだいくつあるのかを数えておくために。
デバッグメソッドで使われるログファイルの名前か、ファイル識別子を 引き出すために。
全体的なデータにアクセスするために。 たとえば、与えられた日の、ネットワークの全 ATM で支払われた現金の 総金額のようなものにアクセスするために。
クラスによって、最後に作られたオブジェクトにアクセスするために、 また、もっともアクセスされたオブジェクトにアクセスするために、 また、すべてのオブジェクトのリストを検索するために。
本当のグローバル変数とは違って、クラス属性は直接にアクセスされるべきでは ありません。 直接にアクセスされるべきではありませんが、クラス属性の状態は 調べられるべきですし、たぶん、変更もされるべきです。 ただし、クラスメソッド を仲介して、アクセスすることを通してのみです。 クラス属性のアクセサメソッドは、気持ちと、機能の上では、 オブジェクトのインスタンス属性の状態を操作するのに使われるアクセサに 似ています。 これらは、インターフェースと実装の間に透明なファイアウォールを提供します。
クラス属性へのアクセスは、クラスの名前か、クラスのオブジェクトのどちらを 通してもできるようにすべきです。 $an_object が、Some_Class の型のもので、 &Some_Class::population_count メソッドがクラス属性にアクセスするとします。 そうすると、2 つの呼び出しが両方とも可能であるべきです。 そして、もちろん、ほとんど同じであるべきです。
Some_Class->population_count()
$an_object->population_count()
疑問があります。 このメソッドがアクセスする、その状態をどこに置いておくのでしょうか? C++ のような、より厳格な言語とは違って、Perlには静的なデータメンバと 呼ばれる場所はありません。 Perl は、クラス属性を宣言するのに、総合のメカニズムはまったく提供しません。 インスタンス属性を宣言する総合のメカニズムしかありません。 Perl は開発者に、特殊な状況の要求にも、うまく作ることのできる、 強力ですが、柔軟な、大まかなセットを提供します。
Perlのクラスは、典型的にモジュールに実装されます。 モジュールは、2 つの相補的な特徴の組合わせで、できています:外の世界との インターフェースのためのパッケージと、プライバシーのためのレキシカルな ファイルスコープのセットです。 これらの 2 つのメカニズムの両方を、クラス属性の実装に使うことができます。 このことは、すなわち、クラス属性をパッケージ変数に置くか、 レキシカル変数に置くかを決めることができるということです。
また、これらは、作るのにあたっての唯一の判断ではありません。 パッケージ変数を選んだとしても、クラス属性アクセサメソッドに、継承を 無視させるか、継承に反応させることもできます。 レキシカル変数を選んだとしても、まったく、ファイルスコープの中の どこからでも、クラス属性にアクセスすることを許すこともできますし、また、 直接データにアクセスするのを、 排他的に、それらの属性を実装するメソッドだけに制限することも出来ます。
難しい問題を解決するための、もっとも簡単な方法の一つは、誰か他の人に 問題を解決させることです! この場合、Class::Data::Inheritable (お近くの CPAN で利用できます)が、 クロージャを使うクラスデータの問題の決まりきった解決法を提供します。 ですので、このドキュメントを苦労して読み進める前に、 Class::Data::Inheritable モジュールを見ることを検討してください。
Perlのクラスは、本当に、ただのパッケージなので、クラス属性を持つのに、 パッケージ変数を使うのはもっとも自然な選択です。 つまり、パッケージに、単純にそれ自身のクラス属性をもたせるのです。 例えば、クラス全体にグローバルであるような、異なった属性がいくつか必要な Some_Class というクラスがあるとします。 もっとも簡単なのは、$Some_Class::CData1 や$Some_Class::CData2 のような パッケージ変数を使い、これらの属性を持たせることです。 ですが、もちろん部外者がこれらのデータディレクトリに触ることを 勧めたくはありません。 ですので、アクセスを仲介するメソッドを提供します。
下の、アクセサメソッドで、今のところ、第一引数 -- メソッド呼び出しの矢印の 左の部分で、クラスの名前か、オブジェクトリファレンス -- を、無視しています。
package Some_Class;
sub CData1 {
shift; # XXX: クラス/オブジェクト呼び出しを無視します
$Some_Class::CData1 = shift if @_;
return $Some_Class::CData1;
}
sub CData2 {
shift; # XXX: クラス/オブジェクト呼び出しを無視します
$Some_Class::CData2 = shift if @_;
return $Some_Class::CData2;
}
このテクニックは、非常に読みやすく、普通のPerlプログラマにとってさえ、 完全に直接です。 パッケージ変数を完全に修飾することで、コードを読むときに、はっきりと、 目立ちます。 不運なことに、もし、これらの一つでも、スペルを間違えたら、捕まえがたい エラーを持ち込むことになります。 また、クラスの名前自身を多くの場所にハードコードしているのを見るのは、 いくぶんイライラもさせます。
この 2 つの問題は、両方とも簡単に修正できます。 use strict
プラグマを加え、それから、パッケージ変数を先に 宣言するだけです。 (our
演算子は、5.6 の、新しいものです。 my
がスコープされたレキシカル変数に働くのと同じように、パッケージの グローバル変数に働きます。)
package Some_Class;
use strict;
our($CData1, $CData2); # our() is new to perl5.6
sub CData1 {
shift; # XXX: クラス/オブジェクト呼び出しを無視します
$CData1 = shift if @_;
return $CData1;
}
sub CData2 {
shift; # XXX: クラス/オブジェクト呼び出しを無視します
$CData2 = shift if @_;
return $CData2;
}
他のどのグローバル変数とも同じように、 これらパッケージ変数を大文字で始めるのを好むプログラマがいます。 このことは、いくぶん明瞭さを助けますが、パッケージ変数がまったく 修飾されていないので、コードを読むときにその重要性が失われるかもしれません。 これを修正するのは簡単で、ここで使われているよりも、もっと良い名前を選べば 事足ります。
ちょうど、インスタンス属性のアクセサメソッドを思慮なく列挙するのが、 最初の数回の後には、飽き飽きするものになっていくように(perltoot を参照)、 クラスデータのアクセサメソッドをリストするときも、 同じことのくり返しに、非常にイライラしてくるでしょう。 くり返しは、プログラマの主要な美徳、不精に反します。 ここでは、あらゆるプログラマが感じる生まれつきの衝動が、 出来るかぎりコードの二重化を睨むものとして現れます。
次のようにすべきです。 最初に、全てのクラス属性を持つハッシュをただ一つ、作ります。
package Some_Class;
use strict;
our %ClassData = ( # our() is new to perl5.6
CData1 => "",
CData2 => "",
);
クロージャ(perlref 参照)を使って、パッケージのシンボルテーブル (perlmod 参照)に直接にアクセスします。 さて、%ClassData ハッシュの中のそれぞれのキーごとに、アクセサメソッドを クローニングします。 これらのメソッドは、値を持ってくるか、特定の指定されたクラス属性に値を 格納するために使われます。
for my $datum (keys %ClassData) {
no strict "refs"; # パッケージに新しいメソッドを登録するために
*$datum = sub {
shift; # XXX: クラス/オブジェクトの呼出を無視する
$ClassData{$datum} = shift if @_;
return $ClassData{$datum};
}
}
&AUTOLOADメソッドを用いる解決法も、うまくはいきますが、このアプローチを 満足の行くものであると立証するのは、見込がありません。 関数はクラス属性とオブジェクト属性の間を区別しなければなりません。 継承に干渉します。 また、DESTORY に注意する必要があります。 このような複雑性は、ほとんどの場合でお呼びではありません。 もちろん、この場合も。
なぜ、strict refs をループで無効にしているのだろうと思うかも知れません。 パッケージのシンボルテーブルを操作して、新しい関数名をシンボリック リファレンスを使って(間接的な命名で)導入しています。 シンボリックリファレンスは、strict プラグマが禁じているものです。 ふつうは、シンボリックリファレンスは、せいぜい危なっかしい考えです。 ただ、意図しないときに、あやまって使われる可能性があるので、危なっかしいと いうだけです。 初心者の Perl プログラマがシンボリックリファレンスを置こうとする、 使いかたのほとんどには、ネストされたハッシュや配列のハッシュのような、 より良いアプローチがあるからです。 ですが、メソッド名やパッケージ変数のようなパッケージのシンボルテーブルの 観点からのみ、意味があることを操作するのに、シンボリックリファレンスを 使うのは、何も間違っていません。 言い替えれば、シンボルテーブルを参照したい場合は、シンボリックリファレンスを 使ってください。
一つの場所に、全てのクラス属性を集積するのは、さまざまな利点があります。 クラス属性は、目に付きやすく、初期化しやすく、変更しやすい。 集約することで、クラス属性を外部から、例えば、デバッガや永続的な パッケージから、アクセスするのが便利になります。 唯一ありそうな問題は、それぞれのクラスが持っているはずの クラスオブジェクトの名前を自動的には知らないことです。 この問題は、下の方の "The Eponymous Meta-Object" に、扱われています。
派生クラスのインスタンスがあったとして、継承されたメソッド呼び出しを使い、 クラスデータにアクセスします。 このとき、結局、基底クラスの属性を参照することになるべきなのか、 派生クラスの属性を参照することになるべきでしょうか? さっきの例ではどのように動くでしょうか? 派生クラスは基底クラスの全てのメソッドを継承します。 クラス属性にアクセスするメソッドも含みます。 ですが、どのパッケージにクラス属性が格納されるのでしょうか?
答えは、書かれたように、クラス属性はそれらのメソッドがコンパイルされた パッケージに、格納されます。 &CData1 メソッドを、派生クラスの名前か、派生クラスのオブジェクトで呼び出すと、 上で示されたバージョンのものが動きます。 つまり、$Some_Class::Cdata1 -- または、クローニングしているバージョンの メソッドで、$Some_Class::ClassData{CData1}
に、アクセスできます。
派生クラスのコンテキストではなく、 基底クラスのコンテキストで実行するようなクラスメソッドを考えましょう。 お望みのものにびったりなことがあります。 猫は肉食動物の下位に分類されれば、新たに猫が生まれると、世界の肉食動物の 固体数が増えます。 ですが、肉食動物とは切り離して、何匹の猫がいるかを数えたいなら どうしましょう? 現在のアプローチではそれには対応できません。
クラス属性が package-relative であることに筋が通っているかどうかを、 ケースバイケースの根拠で決める必要があるでしょう。 package-relative でありたいなら、関数の第一引数を無視するのをやめます。 第一引数は、メソッドがクラス名で直接に呼ばれたなら、パッケージ名、 オブジェクトリファレンスで呼ばれたなら、オブジェクトリファレンスの、 どちらかです。 後者では、ref() 関数がオブジェクトのクラス名を与えます。
package Some_Class;
sub CData1 {
my $obclass = shift;
my $class = ref($obclass) || $obclass;
my $varname = $class . "::CData1";
no strict "refs"; # to access package data symbolically
$$varname = shift if @_;
return $$varname;
}
以前にしたようなコンパイルしているパッケージではなく、呼び出している パッケージのパッケージ変数としてアクセスしたいと思っている、 他の全てのクラス属性(CData2 などのように)と同じようにしたいでしょう。
ここで、再び、一時的に strict references を禁止するのをやめましょう。 そうしないと、パッケージのグローバル変数のために、完全に適格なシンボル名を 使うことが出来ないからです。 このことは、完全に合理的です:全てのパッケージ変数は、定義により、 パッケージに生きています。 パッケージのシンボル名経由で、パッケージ変数に アクセスするのは何も間違っていません。 そのためにそれがあるのです(たぶん)。
全てのもののために、ただ一つのハッシュを使い、メソッドを複製しては どうでしょうか? どのように見えるでしょう? 唯一の違いは、クラスのシンボルテーブルに新しいメソッドを生み出すのに、 クロージャが使われていることです。
no strict "refs";
*$datum = sub {
my $obclass = shift;
my $class = ref($obclass) || $obclass;
my $varname = $class . "::ClassData";
$varname->{$datum} = shift if @_;
return $varname->{$datum};
}
前の例の、%ClassDataハッシュは、変数名が創意に富んでも、直観的でもないと、 主張されるかもしれません。 もっと意味があるか、有益なものか、その両方を備えたものはないでしょうか?
はい、ちょうどよくあります。 "クラスのメタオブジェクト"に、パッケージと同じ名前のパッケージ変数を 使いましょう。 Some_Class のパッケージ宣言のスコープ内で、クラスのメタオブジェクトとして、 %Some_Class という、クラスの名前のハッシュを使いましょう。 (クラスの名前のハッシュは、コンストラクタをクラスの名前にする、 Python や、C++ 流儀のクラスをちょっと思い出させます。 つまり、Some_Class クラスが &Some_Class::Some_Class をコンストラクタと するということです。 おそらく、その名前を同様にエクスポートさえしているでしょう。 例をお探しなら、The Perl Cookbook の StrNum クラスは、そのように しています)。
このありきたりなアプローチには、多くの利益があります。 よくわかる一意の名前を含むことで、デバッグや透過的な永続生やチェックポイントの 助けになります。 後で述べる、一価のクラスと半透明な属性にとっても、明らかな名前です。
下のものは、そのようなクラスの例です。 メタオブジェクトを格納しているハッシュの名前が、どのようにして、 クラスを実装するのに使われているパッケージの名前と同じに しているかに気を付けてください。
package Some_Class;
use strict;
# 完全な名前を使ってクラスのメタオブジェクトを作る
our %Some_Class = ( # our() is new to perl5.6
CData1 => "",
CData2 => "",
);
# このアクセサはcalling-package-relative
sub CData1 {
my $obclass = shift;
my $class = ref($obclass) || $obclass;
no strict "refs"; # to access eponymous meta-object
$class->{CData1} = shift if @_;
return $class->{CData1};
}
# こちらのアクセサは違います
sub CData2 {
shift; # XXX: ignore calling class/object
no strict "refs"; # to access eponymous meta-object
__PACKAGE__ -> {CData2} = shift if @_;
return __PACKAGE__ -> {CData2};
}
2 番目のアクセサで、__PACKAGE__ 記法が、2つの理由から使われています。 1 つ目の理由は、後で、名前を変えたくなった際に、コードにパッケージの名前を リテラルでハードコーディングするのは避けたいからです。 2 つ目の理由は、コードを読む人に、ここで何が問題なのかをはっきり させるためです。 ここでの問題は、現在コンパイルされているパッケージであり、 オブジェクトかクラスを呼び出したパッケージではないということです。 アルファベットでない文字列が長く続くのが面倒なら、 常に、変数の最初に、__PACKAGE__ を置いてください。
sub CData2 {
shift; # XXX: クラス/オブジェクトの呼び出しを無視し、
no strict "refs"; # クラスの名前のメタオブジェクトにアクセスします。
my $class = __PACKAGE__;
$class->{CData2} = shift if @_;
return $class->{CData2};
}
ずっとシンボリックリファレンスを使っていますが、悪くはありません。 ずいぶん多くの場所で、strict ref のチェックを無効にしているのに、 うろたえがちな人がいるでしょうけど。 シンボリックリファレンスであれば、常に、本当のリファレンスを作れます (その逆は真ではありませんが)。 このシンボリックリファレンスから本当のリファレンスへの変換をする サブルーチンを作りましょう。 引数無しで呼ばれたら、コンパイルしているクラスの名前のハッシュへの リファレンスを返します。 クラスメソッドとして呼ばれたら、その呼び出したクラスの名前のハッシュへの リファレンスを返します。 オブジェクトメソッドとして呼ばれたら、この関数は、オブジェクトが 属している、どんなクラスでも、クラスの名前のハッシュへの リファレンスを返します。
package Some_Class;
use strict;
our %Some_Class = ( # our() is new to perl5.6
CData1 => "",
CData2 => "",
);
# tri-natured: function, class method, or object method
sub _classobj {
my $obclass = shift || __PACKAGE__;
my $class = ref($obclass) || $obclass;
no strict "refs"; # シンボリックリファレンスを本当のリファレンスへと変換する
return \%$class;
}
for my $datum (keys %{ _classobj() } ) {
# strict refs をオフにするので、
# シンボルテーブルにメソッドを登録できます。
no strict "refs";
*$datum = sub {
use strict "refs";
my $self = shift->_classobj();
$self->{$datum} = shift if @_;
return $self->{$datum};
}
}
クラス属性を取り扱うの合理的で一般的な作戦とは、 オブジェクトそれ自身で、それぞれのパッケージ変数へのリファレンスを 格納することです。 これは、おそらく既にみた作戦です。 perltoot や、perlbot にあるようなものです。 ですが、まだ考えたことのないようなバリエーションの例が下にあります。
package Some_Class;
our($CData1, $CData2); # our() is new to perl5.6
sub new {
my $obclass = shift;
return bless my $self = {
ObData1 => "",
ObData2 => "",
CData1 => \$CData1,
CData2 => \$CData2,
} => (ref $obclass || $obclass);
}
sub ObData1 {
my $self = shift;
$self->{ObData1} = shift if @_;
return $self->{ObData1};
}
sub ObData2 {
my $self = shift;
$self->{ObData2} = shift if @_;
return $self->{ObData2};
}
sub CData1 {
my $self = shift;
my $dataref = ref $self
? $self->{CData1}
: \$CData1;
$$dataref = shift if @_;
return $$dataref;
}
sub CData2 {
my $self = shift;
my $dataref = ref $self
? $self->{CData2}
: \$CData2;
$$dataref = shift if @_;
return $$dataref;
}
上に書いたように、派生クラスが、これらのメソッドを継承します。 これらのメソッドは基底クラスのパッケージのパッケージ変数に持続的に アクセスします。 このことは、全ての状況で、必要とされ、期待された振る舞いではありません。 変数、メタオブジェクトを使う例があります。 適当なパッケージのデータへのアクセスを解決します。
package Some_Class;
use strict;
our %Some_Class = ( # our() is new to perl5.6
CData1 => "",
CData2 => "",
);
sub _classobj {
my $self = shift;
my $class = ref($self) || $self;
no strict "refs";
# get (hard) ref to eponymous meta-object
return \%$class;
}
sub new {
my $obclass = shift;
my $classobj = $obclass->_classobj();
bless my $self = {
ObData1 => "",
ObData2 => "",
CData1 => \$classobj->{CData1},
CData2 => \$classobj->{CData2},
} => (ref $obclass || $obclass);
return $self;
}
sub ObData1 {
my $self = shift;
$self->{ObData1} = shift if @_;
return $self->{ObData1};
}
sub ObData2 {
my $self = shift;
$self->{ObData2} = shift if @_;
return $self->{ObData2};
}
sub CData1 {
my $self = shift;
$self = $self->_classobj() unless ref $self;
my $dataref = $self->{CData1};
$$dataref = shift if @_;
return $$dataref;
}
sub CData2 {
my $self = shift;
$self = $self->_classobj() unless ref $self;
my $dataref = $self->{CData2};
$$dataref = shift if @_;
return $$dataref;
}
今、strict ref を取り除いているだけでなく、クラスの名前のメタオブジェクトを 使って、コードクリーナーを作っているようです。 前のバージョンとは違い、これは、継承に直面すると、ちょっと面白いことを します: これは、呼び出しているクラスの、クラスのメタオブジェクトにアクセスします。 メソッドが最初にコンパイルされたクラスではありません。
クラスのメタオブジェクトのデータに簡単にアクセスすることが出来ます。 デバッギングや、永続クラスを実装するときのような外部のメカニズムを使って、 完全なクラスの状態を簡単にダンプできます。 このように働くのは、クラスのメタオブジェクトがパッケージ変数であり、 よく知られた名前を持ち、全てのそのデータを一緒にまとめているからです。 (透過的な永続生は常に適しているわけではありませんが、魅力的なアイデアで あるのは確かです。)
まだ、オブジェクトのアクセサメソッドがクラスの名前で呼び出されていないことを チェックしていません。 strict ref のチェックが有効なら、壊れます。 そうでなければ、クラスの名前のメタオブジェクトを得ます。 それを我慢するか、なんとかするかは、あなた次第です。 次の 2 つのセクションで、この強力な特徴の画期的な利用法を、 デモンストレーションします。
Perlに載っている標準モジュールには、何の属性メソッドも一切ない、 クラスインターフェースを提供しているものがいくつかあります。 もっとも普通に使われているモジュールは、プラグマを数にいれなければ、 Exporter モジュールです。 このモジュールは、コンストラクタも、属性もないクラスです。 Exporter の仕事は、単純に、モジュールが呼び出しもとに自分の名前空間の一部を エクスポートする、 標準のインターフェースを提供することです。 モジュールは 、それぞれのモジュールの@ISA配列に継承のリストをセットすることで、 Exporter の &import メソッドを使い、"Exporter" に記載します。 ですが、クラスの Exporter には、コンストラクタがありません。 そのため、複数のクラスのインスタンスを持つことが出来ません。 実際に、一つももてません -- 全く意味をなさない。 あなたが得る全てのものは、そのメソッドです、 そのインターフェースには、なんの状態も含みません。 ですので、状態のデータはまったく不要なのです。
別の種類のクラスで、時間から時間までを取得するモジュールは、 単一のインスタンスをサポートするものです。 このようなクラスは 一価のクラス と呼ばれます。 または、あまり正式ではありませんが、singletons とか、 highlander classesと 呼ばれます。
クラスが一価であれば、その状態、すなわち、属性をどこに格納するのでしょう。 一つのインスタンスより他にはないと、どのように確認するでしょうか? たくさんのパッケージ変数を単に使うこともできますが、 クラスの名前のハッシュを使うクリーナーを使うことも非常にあります。 一価のクラスの完全な例です:
package Cosmos;
%Cosmos = ();
# "name" 属性のアクセサメソッド
sub name {
my $self = shift;
$self->{name} = shift if @_;
return $self->{name};
}
# "birthday" 属性の読み込みのみのアクセサメソッド
sub birthday {
my $self = shift;
die "can't reset birthday" if @_; # XXX: croak() is better
return $self->{birthday};
}
# "stars" 属性のアクセサメソッド
sub stars {
my $self = shift;
$self->{stars} = shift if @_;
return $self->{stars};
}
# おや、まあ! - 星が一つ、死んでしまった!
sub supernova {
my $self = shift;
my $count = $self->stars();
$self->stars($count - 1) if $count > 0;
}
# コンストラクタ/イニシャライザ - リブートで直される
sub bigbang {
my $self = shift;
%$self = (
name => "the world according to tchrist",
birthday => time(),
stars => 0,
);
return $self; # はい、おそらくクラスです。
びっくり!
}
# クラスがコンパイルされた後、ですが、use も require も戻る前、
# バンとともに、宇宙が動き始めます。
__PACKAGE__ -> bigbang();
そのままでいてください。 何も特別なようにはみえません。 これらの属性アクセサは一価のクラスであるか、普通のクラスであるかで、 することは何も違いません。 ここでの最重要ポイントは $self がオブジェクトに bless されたリファレンスを 持たなければならないとは言わないところです。 単に、メソッドを呼ぶことができれば、何でもいいのです。 パッケージの名前自身、Cosmos でも、オブジェクトのように働きます。 &supernova メソッドをみてください。 これは、クラスメソッドか、オブジェクトメソッドでしょうか? その答えは、静的な解析ではその答えを知らせることはできないということです。 Perlは、気にしませんし、あなたも気にしないべきです。 3 つの属性メソッドで、%$self
は、実際は、%Cosmos パッケージ変数に アクセスしています。
ステファン・ホーキングのように、複数の、連続の、関係のない宇宙の存在を 仮定するなら、 &bigbang メソッドを自分で呼び、いつでも、そっくり再び、全てを 始めることができます。 &bigbang を、コンストラクタよりも、もっとイニシャライザとして、 考えているかもしれません。 &bigbang は、新たのメモリを割り当てません、すなわち、&bingbang は、既に そこにあるものを、初期化するだけというために。 ですが、他のどんなコンストラクタと同じように、後のメソッド呼び出しに 使うために、スカラ値をを返します。
将来のいつかに、一つの宇宙では十分ではないと、決心したとしましょう。 スクラッチから新しいクラスを書くこともできますが、 既存のクラスがあります -- 一価であることを除いて、望むクラスがあり、 しかも、ただ、2 つ以上の宇宙が欲しいだけです。
サブクラスでコードの再利用することが全てです。 新しいコードがどのくらい短いか、見てみましょう。
package Multiverse;
use Cosmos;
@ISA = qw(Cosmos);
sub new {
my $protoverse = shift;
my $class = ref($protoverse) || $protoverse;
my $self = {};
return bless($self, $class)->bigbang();
}
1;
Cosmosクラスを設計している時に、慎重であり、良い小さなクリエータであったので、 Multiverse クラスを書く際に、コードの一行もいじらないで、Cosmos クラスを 再利用することができます。 クラスメソッドとして呼ばれたときに動く同じコードが派生クラスの個々の インスタンスから呼ばれても、完全に、うまく、動き続けます。
上にあげた、Cosmos クラスでびっくりするのは、&bigbang "コンストラクタ" が 返す値が、まったく、bless されたオブジェクトへのリファレンスではないことです。 &bigbang コンストラクタが返す値は、クラス自身の名前です。 クラス名は、全ての目的と手段にとって、実質的に、完全に条件を満たしている オブジェクトです。 状態、振舞、同一性、3つの、オブジェクトシステムの重大な構成要素が、 クラス名にはあります。 クラス名は、継承、ポリモルフィズム、カプセル化さえも明らかにします。 オブジェクトにこれ以上何を要求できますか?
Perl でオブジェクト指向を理解するために、他のプログラミング言語が クラスメソッドとオブジェクトメソッドについて考えていることを、ただの単純な メソッドに統一することを認識するのが重要です。 "クラスメソッド"と、"オブジェクトメソッド"は、Perl プログラマが心の中で 区切っているだけで、Perl 言語それ自身の中では、区切られていません。
これらの同じ行の間で、コンストラクタは、何も特別なものではありません。 Perlには、それらの行に、神によって定められている名前はないのがその理由です。 "コンストラクタ"は公式ではない述語であり、後でメソッドを呼ぶことのできる スカラー値を返すメソッドを説明するのを使います。 クラス名もオブジェクトリファレンスでありさえすれば、十分、上等です。 おろしたてのオブジェクトがリファレンスである必要さえないのです。
自分が望むのと同じくらい、多くの -- または、ほとんどない -- 、コンストラクタを 持つことができますし、自分がしたいように何でも名前を付けることができます。 やみくもに、素直に、いままで書いた、ありとあらゆるコンストラクタに、 new() を使うことは、両方の言語にひどい仕打をする、厳格な C++ の アクセントのようなもので、Perl を喋ることです。 それぞれのクラスに一つのコンストラクタがあるとか、または、コンストラクタは new() と名付けられるとか、コンストラクタはオブジェクトメソッドではなく、 ただクラスメソッドとして使われるとか、そういうことを強く主張する理由は まったくありません。
次のセクションでは、コンストラクタとアクセサメソッドの両方で、 クラスメソッドとオブジェクトメソッドの呼び出しとの間で、 正式な区別から、我々自身がさらに遠くまでありうるということが、 どれくらい有益であるかを示します。
パッケージの名前のハッシュは、単にクラス単位のグローバルな状態のデータを 持つこと以上のことに使えます。 オブジェクト属性のデフォルトの設定を持つ、一種のテンプレートとしての役目も 出来ます。 これらのデフォルトの設定は、コンストラクタで、特定のオブジェクトの初期化の ために使うことができます。 クラスの名前のハッシュは、半透明の属性 を実装するのにも使えます。 半透明の属性とは、クラスワイドのデフォルトを持つ属性です。 それぞれのオブジェクトは属性にそれ自身の値をセットできます、 そこでは、$object->attribute()
で、値を返します。 ですが、値がまだセットされていなければ、$object->attribute()
は、 クラスワイドのデフォルトを返します。
これらの半透明の属性に、コピー・オン・ライトのアプローチの何かを 適用するでしょう。 これらの属性からただ、値を取りだしているならば、半透明性を得ます。 ですが、これらの属性に新しい値を蓄えるなら、新しい値は現在のオブジェクトに セットされます。 一方、クラスをオブジェクトとして使い、直接にクラスで属性値を蓄えるなら、 メタオブジェクトの値が変更され、その後で、それらの属性の値が 初期化されていないオブジェクトで取り出し操作を行うと、 メタオブジェクトの新しい値を取り出すことになります。 しかし、オブジェクトが自身の初期化値を持つ場合は、変更は見えません。
どのように実装するかを見せる前に、これらのプロパティを使う、具体的な例を 見てみましょう。 Some_Class という名前のクラスに、"color" という半透明のクラス属性が あるとします。 まず、meta-object のカラーをセットします。 それから、コンストラクタを使って、3つ のオブジェクトをつくります。 コンストラクタは、たまたま &spawn という名前です。
use Vermin;
Vermin->color("vermilion");
$ob1 = Vermin->spawn(); # so that's where Jedi come from
$ob2 = Vermin->spawn();
$ob3 = Vermin->spawn();
print $obj3->color(); # prints "vermilion"
それぞれのオブジェクトの色は、現在、"vermilion(朱色)" です。 メタオブジェクトのその属性の値がそれであり、それぞれのオブジェクトに個別の 色の値がセットされていないからです。
あるオブジェクトの属性を変更しても、先に作られた他のオブジェクトの値には 影響しません。
$ob3->color("chartreuse");
print $ob3->color(); # prints "chartreuse"
print $ob1->color(); # prints "vermilion", translucently
$ob3 を使い、他のオブジェクトを産んでみると、新しいオブジェクトは、 その親が持っている色を取るでしょう。 今は、たまたま、"chartreuse(淡黄緑色)" になるでしょう。 コンストラクタが、初期化の属性のテンプレートとして、呼び出したオブジェクトを 使うからです。 呼び出すオブジェクトがクラス名であれば、オブジェクトは、クラスの メタオブジェクトを参照します。 呼び出すオブジェクトが説明されたオブジェクトへのリファレンスであれば、 &spawn コンストラクタは、テンプレートとして、存在するオブジェクトを使います。
$ob4 = $ob3->spawn(); # $ob3 now template, not %Vermin
print $ob4->color(); # prints "chartreuse"
テンプレートオブジェクトにセットされる実際の値は、どんなものも、 新しいオブジェクトにコピーされます。 ですが、テンプレートオブジェクトに未定義の属性は、半透明になり、未定義の ままとなり、その結果、新しいものも同様に半透明です。
今度は、クラス全体のcolor属性を変更しましょう:
Vermin->color("azure");
print $ob1->color(); # prints "azure"
print $ob2->color(); # prints "azure"
print $ob3->color(); # prints "chartreuse"
print $ob4->color(); # prints "chartreuse"
この色の変更はオブジェクトの最初の2つにのみ、影響します。 この 2 つは、まだ、半透明で、メタオブジェクトの値にアクセスします。 後の 2 つは、オブジェクトごとに、初期化された色がありました。 そのため、変更されません。
一つ、重要な疑問があります。 メタオブジェクトの値を変えることは、クラス全体の半透明の属性に反映されます。 ですが、オブジェクトを絶やす変更はどうでしょうか? $ob3 の色を変更したら、$ob4 にはその変更がわかるでしょうか? また、逆の場合も同じく。 もし、$ob4 の色を変更したら、$ob4 の値は変わるでしょうか?
$ob3->color("amethyst");
print $ob3->color(); # prints "amethyst"
print $ob4->color(); # hmm: "chartreuse" or "amethyst"?
まれなケースでは、そうすべきであるという議論もありますが、 そうしないでおきましょう。 良識の問題は置いておいて、上記のコメントで提示されている質問で "amethyst" ではなく "chartreuse" になる答えが必要です。 環境変数、ユーザ ID やグループ ID や、fork() を超えて扱われるカレント ワーキングディレクトリといったプロセス属性処理するのと似たような方法で、 それらの属性を扱いましょう。 自分自身のみ変更できますが、これらの変更が、自身が作ったのではない 子に反映されることになります。 一つのオブジェクトの変更は、親オブジェクトや、存在する子オブジェクトには 伝播しません。 しかし、後で作られたこれらのオブジェクトは、変更がわかります。
実際の属性値でオブジェクトを持ち、オブジェクトの属性値を再び半透明に したければ、どうしましょうか? その引数に undef
を指定してアクセサメソッドを呼ぶとき、 その属性が再び半透明性になるようにクラスを設計しましょう。
$ob4->color(undef); # back to "azure"
上で説明した Vermin の完全な実装です。
package Vermin;
# here's the class meta-object, eponymously named.
# it holds all class attributes, and also all instance attributes
# so the latter can be used for both initialization
# and translucency.
our %Vermin = ( # our() is new to perl5.6
PopCount => 0, # capital for class attributes
color => "beige", # small for instance attributes
);
# constructor method
# invoked as class method or object method
sub spawn {
my $obclass = shift;
my $class = ref($obclass) || $obclass;
my $self = {};
bless($self, $class);
$class->{PopCount}++;
# init fields from invoking object, or omit if
# invoking object is the class to provide translucency
%$self = %$obclass if ref $obclass;
return $self;
}
# translucent accessor for "color" attribute
# invoked as class method or object method
sub color {
my $self = shift;
my $class = ref($self) || $self;
# handle class invocation
unless (ref $self) {
$class->{color} = shift if @_;
return $class->{color}
}
# handle object invocation
$self->{color} = shift if @_;
if (defined $self->{color}) { # not exists!
return $self->{color};
} else {
return $class->{color};
}
}
# accessor for "PopCount" class attribute
# invoked as class method or object method
# but uses object solely to locate meta-object
sub population {
my $obclass = shift;
my $class = ref($obclass) || $obclass;
return $class->{PopCount};
}
# instance destructor
# invoked only as object method
sub DESTROY {
my $self = shift;
my $class = ref $self;
$class->{PopCount}--;
}
2、3 の便利になるヘルパーメソッドがあります。 これらは、まったく、アクセサメソッドではありません。 データ属性のアクセシビリティを見つけるのに使われています。 この &is_translucent メソッドは特別なオブジェクト属性が、 メタオブジェクトからくるのかどうかを決めています。 &has_attribute メソッドは、クラスが特別なプロパティを 少しでも実装しているかどうかを見つけます。 存在しないプロパティから、未定義のプロパティを区別するのにも、 使われ得ます。
# detect whether an object attribute is translucent
# (typically?) invoked only as object method
sub is_translucent {
my($self, $attr) = @_;
return !defined $self->{$attr};
}
# test for presence of attribute in class
# invoked as class method or object method
sub has_attribute {
my($self, $attr) = @_;
my $class = ref($self) || $self;
return exists $class->{$attr};
}
より一般的に、アクセサをインストールしたいなら、 パッケージに、一般のクロージャからクローニングされた適切なメソッドを 登録するのに、大文字対小文字の慣習を利用することができます。
for my $datum (keys %{ +__PACKAGE__ }) {
*$datum = ($datum =~ /^[A-Z]/)
? sub { # install class accessor
my $obclass = shift;
my $class = ref($obclass) || $obclass;
return $class->{$datum};
}
: sub { # install translucent accessor
my $self = shift;
my $class = ref($self) || $self;
unless (ref $self) {
$class->{$datum} = shift if @_;
return $class->{$datum}
}
$self->{$datum} = shift if @_;
return defined $self->{$datum}
? $self -> {$datum}
: $class -> {$datum}
}
}
このクロージャベースのアプローチを、C++、Java、Python へ、 転換するのは、読者の課題として残しておきます。 できあがったらすぐに、きっと送ってくださいね。
Perlプログラマの幾人かに使われている慣習とは違い、先の例では、 クラス属性に使われるパッケージ変数の前に、アンダースコアを付けていませんし、 インスタンス属性に使われるハッシュキーの名前にも、そのようにしていません。 データの名前に小さなマークをつけて、属性変数や、ハッシュのキーに、名目上の プライバシーを暗示する必要はありません; というのは、いつも 概念的に プライベートだからです! 部外者が、クラス内で何かをいじるかは、少しも気にしなく良いからです。 そのドキュメントのインターフェースを通すのを除いて;言い換えると、メソッドの 実行を通すのを除いて。 そして、単に任意のメソッドを通して、というわけでもありません。 下線で始まるメソッドは伝統的に、クラスの外側から立入禁止と考えられています。 もし、部外者が、ドキュメントに書かれたメソッドインターフェースを読み飛ばして、 部外者が、クラスの内部をつつきまわすために文書化されている メソッドインターフェースを飛ばして、何かを壊すことになっても、クラスの作者の 落度ではありません。 -- それは、そういうことをした部外者の落度です。
Perl は、統治された制御よりもむしろ、個人の責務を信じています。 Perl は十分にあなたを尊敬しているので、あなたに、自身にあったレベルの痛みや 喜びを選択させます。 Perl は、あなたが、創造的で、知性的で、自分自身で決定でき--そして自身の 行動に完全に責任を取れると期待しています。 完全な世界では、このような勧告だけで十分でしょう。 そして、全ての人が、知性的で、信頼でき、幸福で、創造的であるでしょう。 そして、慎重にしてください。 たぶんある人が慎重であることを忘れても、それは、かなり 予測しがたいことでしょう。 アインシュタインでさえ、うっかり曲がり間違えて、町の間違った地域で 迷うことになりました。
誰でも、パッケージ変数に、手が届き、変更できる、出入りできる、 パッケージ変数に、とても神経質になる人もいます。 誰かが、どこかで、 悪戯をすることに、不断に帯得ている人達です。 この問題の解決は単純に、悪戯を押え込むことです、もちろん。 ですが、不幸なことに、そなんに簡単ではありません。 これらの警告タイプは、彼らや他の人々が、たまたまか、絶望の中からかどうか、 注意なしにそんなにひどくない悪戯をするのにも恐れさせます。 注意不足な人々みんなを解雇するするなら、そこには、すぐに、何かの仕事をする 人は誰も居なくなるでしょう。
それが、必要のないパラノイアか、賢明な警告かどうか、 この不安は人によっては、問題になりえます。 パッケージ変数の代わりにレキシカル変数に、クラス属性を蓄えるオプションを 与えることで、そういう人たちの不安を和らげることができます。 my() 演算子は Perl のすべてのプライバシーの源です。 そして、my() は、プライバシーの強力な形です。 実際に。
次のことは、広く認められていますし、実際、よく書かれています。 Perlは、データの隠蔽を提供しないし、 Perlは、クラスの設計者にプライバシーも孤立も提供しません。 単に、ぼろぎれの、弱点の詰め合わせであり、 押しつけることの出来ない社会的慣習が代わりにあるだけです。 この認識は、確実に間違いであり、ちっとも精査されていません。 次のセクションでは、ほとんどのどんな他のオブジェクト指向言語で 提供されているよりも、はるかに強力なプライバシーの形を実装するのを見せます。
レキシカル変数はその静的なスコープの終りまででしか、見えません。 このことは、ブロックがあればそのブロックの終りまでの、ブロックがなければ 現在のファイルの終りまでの、my() 演算子の下にあるテキストに存在する コードだけが、この変数にアクセス出来る唯一のコードです。
このドキュメントの最初にあった、最も単純な例で始めましょう。 our() 変数を my() バージョンで置き換えます。
package Some_Class;
my($CData1, $CData2); # file scope, not in any package
sub CData1 {
shift; # XXX: ignore calling class/object
$CData1 = shift if @_;
return $CData1;
}
sub CData2 {
shift; # XXX: ignore calling class/object
$CData2 = shift if @_;
return $CData2;
}
あの古い、$Some_Class::CData1 パッケージ変数とその仲間については、 これだけにしておきます! それらは、今や過ぎさって、レキシカル変数に置き換えられました。 スコープの外側にいる誰も、ドキュメントに書かれたインターフェースの手段以外に、 クラスの状態に影響を及ぼせませんし、変更も出来ません。 このサブクラスやスーパークラスでさえ、$CData1 へアクセスを仲介しません。 Some_Class か、そのインスタンスか、他の似たようなもので、 &CData1 メソッドを呼ぶ必要があります。
ものすごく正直になれば、最後のステートメントは、同じファイルスコープに、 複数のクラスを一緒にパックしないし、複数の違うファイルにまたがって、クラスの 実装ばらまきもしないと想定するでしょう。 これらの変数のアクセシビリティは、静的なファイルスコープでユニークに ベースにされています。
クラス属性を 1 つのレキシカルスコープの変数に集約し、構造体を作りたければ、 そうするのは、完全に自由です。
package Some_Class;
my %ClassData = (
CData1 => "",
CData2 => "",
);
sub CData1 {
shift; # XXX: ignore calling class/object
$ClassData{CData1} = shift if @_;
return $ClassData{CData1};
}
sub CData2 {
shift; # XXX: ignore calling class/object
$ClassData{CData2} = shift if @_;
return $ClassData{CData2};
}
他のクラス属性を加えるように、より拡張性をもたせたければ、 再び、パッケージのシンボルテーブルにクロージャを登録し、 それらのためのアクセスメソッドを作れます。
package Some_Class;
my %ClassData = (
CData1 => "",
CData2 => "",
);
for my $datum (keys %ClassData) {
no strict "refs";
*$datum = sub {
shift; # XXX: ignore calling class/object
$ClassData{$datum} = shift if @_;
return $ClassData{$datum};
};
}
何か他のようなアクセサメソッドを使う自分自身のクラスを必要とすることは、 おそらく良い事です。ですが、他のもの全て、サブクラス、スーパークラス、 敵味方、全てが、仲介を通してあなたのオブジェクトにくるのを、 要求したり、予期したりすることは、単なる良い考えを超えるものです。 これは、そのモデルにとって全く重要です。 "public" データも、"protected" データも、どちらも、 そのようなものは心の中からなくしましょう。 それらは、魅惑的ですが、詰まるところ、有害な考えです。 両方とも、戻って来て、あなたに噛みつくでしょう。 全ての状態が完全にプライベートだと考えられ、それ自身のアクセサメソッドの 観点から保存される、安定した位置から外れるとすぐに、そのエンベロープを 破ることになるからです。 カプセル化されているエンベロープに穴を開けていると、 未来の実装の変更が関係ないコードを壊す時、誰かが代価を払うことは、 間違いありません。 この不完全な結果を避ける考えは、まさに、最初の場所でオブジェクト志向に 変化することによる卑屈な抽象化の批判を被るのに納得する理由であり、 そのような破損はとても残念なことに思えます。
Some_Class が、派生の Another_Class から基底クラスとして 使われているとしましょう。 &CData メソッドを、派生クラスか、派生クラスのオブジェクトで呼んだら、 何を得るでしょうか? 派生クラスは、その自身の状態があるのか、それとも、 基底クラスのクラス属性の変数におんぶするのでしょうか?
その答えは上で要点をまとめた枠組のもとにあります。 派生クラスはそれ自身の状態データがあり ません。 前のように、これを良いことと思うか、悪いことと思うか、 関係するクラスの意味によります。
レキシカルで、クラスごとの状態を扱う、最も綺麗な、最も健全で、最も単純な 方法は、クラス属性にアクセスするメソッドの基底クラスのバージョンを 派生クラス用に、オーバーライドすることです。 呼び出される実際のメソッドが、オブジェクトの派生クラスのものなので、 もしあれば、この方法で、クラス後との状態を時動的に得ます。 %ClassData ハッシュへのリファレンスをこっそり外に持ち出すめの、 非公式のメソッドを提供する衝動に、はげしく耐えるべきです。
他のオーバーライドされたメソッドと同様に、派生クラスの実装にはいつも、 自分自身のバージョンとは別に、その基底クラスバージョンのメソッドを 呼び出すオプションがあります。 その例です:
package Another_Class;
@ISA = qw(Some_Class);
my %ClassData = (
CData1 => "",
);
sub CData1 {
my($self, $newvalue) = @_;
if (@_ > 1) {
# set locally first
$ClassData{CData1} = $newvalue;
# then pass the buck up to the first
# overridden version, if there is one
if ($self->can("SUPER::CData1")) {
$self->SUPER::CData1($newvalue);
}
}
return $ClassData{CData1};
}
オーバーライドよりも、多重継承に首をつっこむとが、考えられます。
for my $parent (@ISA) {
my $methname = $parent . "::CData1";
if ($self->can($methname)) {
$self->$methname($newvalue);
}
}
&UNIVERSAL::can メソッドは、関数へのリファレンスを、直接に返します。 ですので、重要なパフォーマンスの改良のために、これを直接に使うことが できます。
for my $parent (@ISA) {
if (my $coderef = $self->can($parent . "::CData1")) {
$self->$coderef($newvalue);
}
}
UNIVERSAL::can
を自身のクラスでオーバーライドする場合は、確実に適切な リファレンスを返すようにしてください。
現在インプリメントされるように、ファイルスコープのレキシカル変数の %ClassData と同じ範囲内のどのコードでも、そのハッシュを直接に、変更できます。 これは良いでしょうか? このクラスの実装の他の部分が、直接にクラス属性にアクセスすることを許すことは、 受け入れられか、望ましいものでしょうか?
このことは、あなたがどれくらい注意深くありたいかに依存します。 Cosmo クラスに戻って、考えてください。 &supernova メソッドが直接に、$Cosmos::Starts か、 $colsmos::Cosmos{stars}
を変更していたら、Multiverse を考案するときに、 クラスを再利用することができなかったでしょう。 クラスそれ自身でさえも、そのクラス属性に、適切に設計されたアクセサメソッドが 仲介している介入なしに、アクセスするのは、結局、良い考えではないでしょう。
クラス自身からクラス属性へのアクセスを制限するのは、ふつう、 強力なオブジェクト指向言語でさえ、強制されていません。 ですが、Perl では、できます。
一つの方法です:
package Some_Class;
{ # scope for hiding $CData1
my $CData1;
sub CData1 {
shift; # XXX: unused
$CData1 = shift if @_;
return $CData1;
}
}
{ # scope for hiding $CData2
my $CData2;
sub CData2 {
shift; # XXX: unused
$CData2 = shift if @_;
return $CData2;
}
}
誰も -- 絶対に誰も -- クラス属性に、支配しているアクセサメソッドの 仲介なしには、クラス属性を読み書きすることを許されていません。 メソッドだけが、支配ているレキシカル変数にアクセスするからです。 この、クラス属性への仲介するアクセスの使用は、ほとんどの、オブジェクト指向 言語が提供するものよりも、はるかに強力な、プライバシーの形です。
データごとの アクセサメソッドを作成するために使用される、コードを 反復するのは、不精を苛立だせるので、 似たようなメソッドを作成するのに、再びクロージャを使用しましょう。
package Some_Class;
{ # scope for ultra-private meta-object for class attributes
my %ClassData = (
CData1 => "",
CData2 => "",
);
for my $datum (keys %ClassData ) {
no strict "refs";
*$datum = sub {
use strict "refs";
my ($self, $newvalue) = @_;
$ClassData{$datum} = $newvalue if @_ > 1;
return $ClassData{$datum};
}
}
}
上のクロージャは、以前に示した、&UNIVERSAL::can メソッドと SUPER を使用して、 継承を考慮に入れるために修正できます。
Veriman クラスは、そのメタオブジェクトとして、パッケージ変数、 クラスの名前の %Vermin を使って、半透明性をデモンストレーションしました。 継承か、もしかすると、Exporter を和らげるそれらの必需品以上に、 絶対に、パッケージでない変数を使いたいなら、この作戦は、身近なものです。 残念なことに、半透明の属性はアピールするテクニックですので、 レキシカル変数のみを使う実装を考え出す価値のあるものです。
ここに、クラスの名前のハッシュを避けたいと思う 2 つ目の理由があります。 それらに、ダブルコロンのクラス名を使うと、 つつくつもりのないどこかを、結局、つつくことになるでしょう。
package Vermin;
$class = "Vermin";
$class->{PopCount}++;
# accesses $Vermin::Vermin{PopCount}
package Vermin::Noxious;
$class = "Vermin::Noxious";
$class->{PopCount}++;
# accesses $Vermin::Noxious{PopCount}
最初のケースでは、クラスの名前にはダブルコロンがなかったので、現在の パッケージのハッシュを得ました。 ですが、次のケースでは、現在のパッケージのハッシュを得る代わりに、 Vermin パッケージの、ハッシュ %Noxious を得ました。 (有毒な害虫は、他のパッケージに侵入し、そのまわりに、自分のデータを 撒き散らします :-) Perl の命名仕様は、相対パッケージを サポートしません。 ですので、ダブルコロンは、現在のパッケージの中を見るだけではなく、 完全修飾された検索を引き起こします。
実際問題、Vermin クラスには、吹き飛ばした、%Noxious という名前の、 存在するパッケージ変数は、ありそうもないです。 まだ、信用できないなら、ルールを知っている自分の領域を、つねに 見張ることができます。 Eponymous::Vermin::Noxious か、Hieronymus::Vermin::Boschious か、 Leave_Me_Alone::Vermin::Noxious を、クラス名として、代わりに、使うように。 確かに、 他の誰かが、Eponymous::Vermin という名のクラスを、 それ自身の %Noxious ハッシュと一緒に持つことは、理論上は可能ですが、 この種類のことは、つねに真実です。 パッケージの名前の得決定者はいません。 2 つ以上のクラスが同じ Cwd パッケージを使うなら、@Cwd::ISA のようなグローバル 変数が衝突するするのは、ありがちなケースです。
パラノイアの不快な苦痛がまだ残っているなら、別の解決法があります。 一価のクラスのために、あるいは半透明の属性のために、 クラスのメタオブジェクトにを持つのに、パッケージ変数を 持たなくてはいけないとするものは何もありません。 代わりに、レキシカルにアクセスするようなメソッドをコードにしてください。
Vermin クラスの別の実装です。 以前に与えられたものと同じ意味ですが、今回は、パッケージ変数を 使っていません。
package Vermin;
# Here's the class meta-object, eponymously named.
# It holds all class data, and also all instance data
# so the latter can be used for both initialization
# and translucency. it's a template.
my %ClassData = (
PopCount => 0, # capital for class attributes
color => "beige", # small for instance attributes
);
# constructor method
# invoked as class method or object method
sub spawn {
my $obclass = shift;
my $class = ref($obclass) || $obclass;
my $self = {};
bless($self, $class);
$ClassData{PopCount}++;
# init fields from invoking object, or omit if
# invoking object is the class to provide translucency
%$self = %$obclass if ref $obclass;
return $self;
}
# translucent accessor for "color" attribute
# invoked as class method or object method
sub color {
my $self = shift;
# handle class invocation
unless (ref $self) {
$ClassData{color} = shift if @_;
return $ClassData{color}
}
# handle object invocation
$self->{color} = shift if @_;
if (defined $self->{color}) { # not exists!
return $self->{color};
} else {
return $ClassData{color};
}
}
# class attribute accessor for "PopCount" attribute
# invoked as class method or object method
sub population {
return $ClassData{PopCount};
}
# instance destructor; invoked only as object method
sub DESTROY {
$ClassData{PopCount}--;
}
# detect whether an object attribute is translucent
# (typically?) invoked only as object method
sub is_translucent {
my($self, $attr) = @_;
$self = \%ClassData if !ref $self;
return !defined $self->{$attr};
}
# test for presence of attribute in class
# invoked as class method or object method
sub has_attribute {
my($self, $attr) = @_;
return exists $ClassData{$attr};
}
継承は強力ですが、繊細な仕組みです。 注意深い見通しと設計の後で使うのが一番です。 継承の代わりに、集約が、よりよいアプローチであることがよくあります。
SelfLoader か、AutoLoader との接続では、ファイルスコープのレキシカル変数を 使えません。 モジュールのメソッドが、結局コンパイルされるレキシカルスコープを 変更するからです。
ふつう当り障りのない、パッケージのマンジングは、確かに、オブジェクト属性の 名前をセットするのに、適合します。 たとえば、$self->{ObData1}
は、おそらく、 $self->{ __PACKAGE__ . "_ObData1" }
であるべきです。 ですが、それは、例を混乱させるだけだったでしょう、
perltoot, perlobj, perlmod, perlbot。
CPAN にある Tie::SecureHash と、Class::Data::Inheritable モジュールは、 良く調べる価値があります。
Copyright (c) 1999 Tom Christiansen. All rights reserved.
This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.
Irrespective of its distribution, all code examples in this file are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.
Russ Allbery, Jon Orwant, Randy Ray, Larry Rosler, Nat Torkington, and Stephen Warren all contributed suggestions and corrections to this piece. Thanks especially to Damian Conway for his ideas and feedback, and without whose indirect prodding I might never have taken the time to show others how much Perl has to offer in the way of objects once you start thinking outside the tiny little box that today's "popular" object-oriented languages enforce.
Last edit: Sun Feb 4 20:50:28 EST 2001