perltoot - トムによる Perl オブジェクト指向チュートリアル
オブジェクト指向プログラミングは、昨今のビッグセラーです。 マネージャーには、薄切りパン(訳註: 良い発明のこと)よりもむしろ オブジェクトを持ちたがる人もいます。 どうしてでしょうか? オブジェクトの、何がそんなに特別なんでしょうか? そもそも、オブジェクトとは一体 何でしょう?
オブジェクトは、きちんとした小さな使いやすい包みに包んで、複雑なふるまいを 遠くへやってしまう方法以外のなにものでもありません。 (専門家は、このことを抽象化と呼びます。) 本当に難しい問題を片付けようと、何週間もぼーっと過ごしている、 賢い人たちは、普通の人でも使える、これらの素敵なオブジェクトを作ります。 (専門家は、このことをソフトウェアの再利用と呼びます。) オブジェクトを使う人(たぶん、プログラマ)は、自分が欲しい小さな包みを いじることができますが、その包みを開けて、中身にちょっかいを 出そうとはしません。 ちょうど、ハードウェアの高価な部分のように、契約は次のようになっています; 「カバーをいじくれば、保証は無効になります。」 ですから、そんなことをしてはいけません。
オブジェクトの核心は、クラスです; これは守られた小さな私的な 名前空間であり、データと関数が詰まっています。 クラスはいくつかの問題領域を扱う関連したルーチンの集合です。 クラスをユーザ定義の型と考えることもできます。 Perl のパッケージの仕組みは、より伝統的なモジュールと同じように、 クラスモジュールにも使われます。 オブジェクトは、クラスの中に、「住んで」います; つまり、 オブジェクトがいくつかのパッケージに属していることを意味します。
たいてい、クラスは、それを使う人にいくつかの小さな包みを提供します。 それらの包みがオブジェクトです。 オブジェクトは、自分が属しているクラスを知っていて、どのように振る舞うかも 知っています。 クラスを使う人は、クラスに、何かをするように、例えば 「オブジェクトをちょうだい」のように、頼むか、 オブジェクトの 1 つに何かをするように頼むことができます。 クラスに何かをするように頼むことは、クラスメソッド を呼ぶことです。 オブジェクトに何かをするように頼むことは、オブジェクトメソッド を 呼ぶことです。 クラス(普通)または、オブジェクト(たまに)に、オブジェクトを返すように 頼むことは、単にメソッドの一種である コンストラクタ を呼ぶことです。
それはそれとして、オブジェクトは Perl の他のデータ型とどのように 違うのでしょうか? 実際には、オブジェクトは…オブジェクトの基礎の型は何なのでしょうか? 最初の質問に答えるのは、簡単です。 オブジェクトは、一つの、たった一つのやり方において、Perl の他のデータ型とは 違っています。 オブジェクトをデリファレンスするのに、単純な配列やハッシュのように、 単なる文字列や数字の添字ではなく、名前の付けられたサブルーチンの呼び出しを 使います。 一言でいえば、メソッド でデリファレンスします。
二番目の答えは、次のようになります。 オブジェクトはリファレンスですが、ただのリファレンスではありません。 注意してください。 特別なクラス(パッケージ)に 祝福(bless)されている リファレンスを 持っているものです。 リファレンスの種類は? たぶん、その質問の答えは、あまり具体的にはなりません。 Perlでは、基礎となる固有のデータ型として、リファレンスならどんな種類の ものでも、クラス設計者の好きなように使うことが出来るからです。 リファレンスなら、スカラでも、配列でも、ハッシュでもかまいません。 コードのリファレンスすらありえます。 ですが、持ち前の柔軟性により、普通、オブジェクトはハッシュリファレンスです。
クラスを作る前に、クラスに何という名前を付けるかを決めなければなりません。 クラス(パッケージ)の名前は、普通のモジュールと同じように、クラスを納める ファイル名を左右します。 それから、そのクラス(パッケージ)は、一つか、それ以上の方法で、オブジェクトを 生成する方法を提供すべきです。 最後に、クラス(パッケージ)は、オブジェクトを使う人に、離れたところから 間接的にオブジェクトを操作することのできるメカニズムを提供すべきです。
例えば、単純な Person クラスモジュールを作ってみましょう。 このクラスモジュールは、Person.pm に保存されます。 もし、このクラスモジュールが、Happy::Person クラスと呼ばれるなら、 このクラスは、Happy/Person.pm ファイルに保存されます。 そして、そのパッケージは、ただの、Person ではなく、Happy::Person になります。 (Unix か、Plan 9 ではなく、Mac OS やVMS のような OS の動いている パーソナルコンピュータ上では、ディレクトリのセパレータは 違っているでしょうが、原則は同じです)。 ディレクトリ名のベースとなるモジュールとの間にどのような公式な関係も 想定してはいけません。 これは、単に、グループ分けを便利にしているだけで、継承や変数の アクセシビリティなど、その他諸々に、何の効果も与えません。
このモジュールに Exporter は使わないでおきましょう。
私たちは行儀が良いし、行儀が良いクラスモジュールはまったく何も
export しないからです。
オブジェクトを作るためには、クラスに、コンストラクタメソッド が必要です。
コンストラクタは、普通のデータ型ではなく、おろしたてのクラスのオブジェクトを
返します。
この魔法は bless()
関数によって扱われます。
bless()
の唯一の目的は、リファレンスをオブジェクトとして使えるように
することです。
銘記:オブジェクトであることは、実際には、メソッドが、そのオブジェクトを
背景にして、呼ばれるということ以上に何も意味しません。
コンストラクタは好きなように名付けても構いませんが、ほとんどの Perl プログラマはコンストラクタを、new() と呼ぶのを好むようです。 ですが、new() は予約語ではありませんし、クラスにはそういったものを供給する 義務もありません。 コンストラクタとして、クラスと同じ名前の関数を使うプログラマにがいることも 知られています。
Pascal のレコードや、C の構造体や、C++ のクラスを表すために、 Perl で使われている、もっとも一般的なメカニズムは、無名ハッシュです。 ハッシュには任意の数のデータ領域があり、自分でつけた任意の名前で、 それぞれにアクセスしやすいからです。
単純な構造体風なエミュレーションをするなら、次のようにすることができます:
$rec = { name => "Jason", age => 23, peers => [ "Norbert", "Rhys", "Phineas"], };
違いをつけたいと感じたら、大文字のハッシュキーによって、 見た目に、ちょっとした違いを加えることができます:
$rec = { NAME => "Jason", AGE => 23, PEERS => [ "Norbert", "Rhys", "Phineas"], };
これで、$rec->{NAME}
で、"Jason" を見付けることができるようになり、
また、@{ $rec->{PEERS} }
で、"Norbert" と、"Rhys" と "Phineas" を
得ることができます。
(昨今、どれくらい多くの、23 才のプログラマが、Jason と
名付けられているか気にしたことがありますか? :-)
これと同じモデルは複数のクラスでよく使われてますが、 とはいっても、クラスの外から、皆がオブジェクトにワルツを踊らせて、 そのデータメンバにずうずうしく直接にアクセスすることは、 プログラミングの礼儀正しさの頂点をよく考えたものではありません。 概して、オブジェクトは オブジェクトメソッド を使ってアクセスする、 不可解なクッキーと考えるべきです。 見た目には、メソッドは、ブラケットや、ブレイスの代わりに、 関数名を使って、リファレンスをデリファレンスしているように見えます。
言語には、クラスのメソッドのための正式な総合のインターフェースを 提供しているものもありますが、Perl はそうではありません。 使う人がそれぞれのクラスのドキュメントを読むのを当てにしています。 定義されていないメソッドをオブジェクトで呼んだら、Perl は、文句を 言おうとはしませんが、プログラムは、実行中に例外をおこすでしょう。 同じように、引数に素数を期待するメソッドに、素数でない数字を引数にして 呼んだとしても、コンパイラがそのことを捕らえてくれることは期待できません。 (たぶん、あなたは、コンパイラにあなたの好むすべてを期待するでしょうが、 そんなことは起こりません。)
Person クラスを使う人が、よく教育された、所定のインターフェースを 説明するドキュメントを読んでいる人だと想定しましょう。 Person クラスの使いかたがここにあります:
use Person;
$him = Person->new(); $him->name("Jason"); $him->age(23); $him->peers( "Norbert", "Rhys", "Phineas" );
push @All_Recs, $him; # 後のために、オブジェクトを配列にいれます。
printf "%s is %d years old.\n", $him->name, $him->age; print "His peers are: ", join(", ", $him->peers), "\n";
printf "Last rec's name is %s\n", $All_Recs[-1]->name;
上のように、そのクラスを使う人は、オブジェクトにある特定の実装があるか、 他の実装があるかを知りません(少なくとも、その事実に注意を払うかは 無関係です)。 クラスとそのオブジェクトへのインターフェースは、専らメソッド経由です。 そして、クラスを使う人はみんな、メソッドをいじるべきです。
まだ、誰か は、オブジェクトに何があるかを知る必要があります。
そして、その誰かとは、クラスです。
クラスはプログラマが、オブジェクトにアクセスするのに使うメソッドを実装します。
ここに示すのは、標準的なオブジェクトのようにハッシュリファレンスを使う
イディオムを使い、Person クラスに実装する方法です。
コンストラクタとして働く new()
というクラスメソッドを作りましょう。
そして、3 つのオブジェクトメソッド、name() と、age() と、peers() を作り、
オブジェクトごとに、無名ハッシュにデータを隠すようにします。
package Person; use strict;
######################################################### ## オブジェクトのコンストラクタ (単純化したバージョン )## ######################################################### sub new { my $self = {}; $self->{NAME} = undef; $self->{AGE} = undef; $self->{PEERS} = []; bless($self); # 下を見て下さい return $self; }
##################################################### ## オブジェクトごとのデータにアクセスするメソッド ## ## ## ## 引数があれば、値をセットする。引数がなければ ## ## 値を返す。 ## #####################################################
sub name { my $self = shift; if (@_) { $self->{NAME} = shift } return $self->{NAME}; }
sub age { my $self = shift; if (@_) { $self->{AGE} = shift } return $self->{AGE}; }
sub peers { my $self = shift; if (@_) { @{ $self->{PEERS} } = @_ } return @{ $self->{PEERS} }; }
1; # require か、use を成功させるために
オブジェクトのデータにアクセスする3つのメソッド、name()、age()、peers() を、 作りました。 これらは、実質は、すべて似たようなものです。 引数つきで呼べば、しかるべきフィールドに、値をセットし、引数がなければ、 そのフィールドに保持されている値を返します。 ハッシュのキーの値を意味します。
たとえ、この時点でその意味することを知らなくてよくても、いつの日か継承に 悩むことになるでしょう。 (お望みなら、今のところ継承を安全に無視し、後で、継承に悩むこともできます)。 継承が、全てスムーズにうまくいくことを保証するには、bless() に2つの引数を 渡さなければいけません。 2 番目の引数はクラスですが、リファレントを祝福(bless)するクラスです。 デフォルトでは、(訳註:2 番目の引数を省略すると)2 番目の引数として自分自身の クラスを想定します。 そのかわりに、(訳註:コンストラクタの第一引数に)渡されるクラスを使います。 こうすることで、コンストラクタは継承できるようになります。
sub new { my $class = shift; my $self = {}; $self->{NAME} = undef; $self->{AGE} = undef; $self->{PEERS} = []; bless ($self, $class); return $self; }
これが、コンストラクタの全てです。 これらのメソッドは、オブジェクトに命をもたらし、きちんとした、小さくて、 不可解な束をユーザに返し、次のメソッドの呼出しに使われます。
全ての話には、始まりがあり、終わりがあります。 オブジェクトの話の始まりには、コンストラクタがあり、オブジェクトが 存在しはじめる時には、明示的にコンストラクタが呼ばれます。 オブジェクトの話の終わりは、デストラクタ ですが、このメソッドは、 オブジェクトがその命を無くすときに暗黙のうちに呼ばれます。 オブジェクトごとの後かたづけのコードがデストラクタにあり、デストラクタは、 (Perl では)DESTROY と呼ばれなければいけません。
コンストラクタは恣意的な名前であるのに、なぜ、デストラクタは 恣意的でないのでしょうか? その理由は、コンストラクタは、明示的に呼ばれますが、デストラクタは そうではないからです。 デストラクタの呼出しは、Perl のガベージコレクションシステム経由で、 自動的に起こります。 素早いですが、幾分、怠惰なリファレンスに基づいた、ガベージ コレクションシステムです。 呼ぶべきものが何かわかるように、Perl は、デストラクタが DESTROY と 名付けられることを要求します。 Perl はデストラクタを呼ぶ適切な時期を考えますが、現在のところいつ 呼ばれるかははっきり定義されていません。 このために、デストラクタは、それがいつ呼ばれるかを当てに すべきではありません。
なぜ、DESTORY はすべて大文字なのでしょう? Perl は、ときどき、慣例で全て大文字の関数名を使います。 この関数は、なんらかの方法で Perl によって自動的に呼ばれるかも しれないことを示します。 DESTROY の他に、暗黙のうちに呼ばれるものには、BEGIN や END や AUTOLOAD、 加えて、perltie に書かれている、tie されたオブジェクトに使われる全ての メソッドなどが含まれます。
本当によいオブジェクト指向プログラム言語では、ユーザは、デストラクタが 呼ばれるときを気にしません。 起こるであろうときに、ただ、起きるのです。 どんなガベージコレクションもまったくない、低レベルの言語では、 適切な時期にデストラクタが呼び出されることをまったく当てに出来ません。 そのため、プログラマは、明示的にデストラクタを呼び、メモリや状態の 後かたづけをする必要があります。 C++ とは違って、オブジェクトのデストラクタは、ほとんど、Perl では 必要とされていません。 そして、デストラクタがあるときでさえ、明示的呼び出しは、ありません。 Person クラスの場合は、デストラクタを必要としません。 というのは、メモリの割り当て解除のような単純な問題は、 Perl が面倒をみるからです。
Perl のリファレンスに基づく、ガベージコレクションが働かない唯一の状況は、 データ構造に循環性があるときです。 次のようなものです:
$this->{WHATEVER} = $this;
プログラムがメモリリークをしないことを期待するなら、この場合、自己参照を 手動で削除しなければなりません。 明らかに、エラーを起こしやすい状態で、これは、ただ今できるベストなことです。 にもかかわらず、プログラムが終わるときに、そのオブジェクトのデストラクタが 全て正式に呼ばれるので、安全が保証されます。 そのため、次のことが保証されます。 プログラムが終了していない唯一の場合をのぞいて、オブジェクトが、 最終的に適切に、破壊されます。 (他のアプリケーションに埋め込んで Perl を走らせているなら、この徹底した ガベージコレクションの通過は、普段より少々頻繁に - スレッドが終了する度に - 起こります)。
今までに話しているメソッドは、コンストラクタか、他は、単純な "データメソッド"であり、 オブジェクトに蓄えられているデータへのインターフェースです。 これらのデータは、C++ での、オブジェクトのデータメンバに少し似ています。 見知らぬ人がデータとして、オブジェクトのデータメンバに アクセスできないことを期待します。 その代わりに、オブジェクトのデータメンバにデータメソッド経由で、 間接的にアクセスすべきです。 このことは Perl で、重要なルールです。 Perl では、オブジェクトのデータへのアクセスは、メソッドを通して のみ なされるべきです。
Perl は、誰がどのメソッドを使えるかの制限を押しつけません。 パブリック と プライベート の違いは慣例によるもので、構文にはよりません。 (たぶん、変数としてのデータメンバ に、下の方で記述する Alias モジュールを 使わない場合は)。 メソッドの名前が一つか、2 つののアンダースコアで始まっているか、 終わっているものを見ることがあるでしょう。 このマークは、慣例的に次のことを示します。 そのメソッドがそのクラスだけ、および、密接なその知合い、つまりサブクラスの プライベートなものであることを示します。 ですが、この区別は Perl 自身によって強制されてはいません。 そう振る舞うことは、プログラマ次第です。
メソッドを単純なデータにアクセスするものだけに限定する理由はありません。 メソッドは、まったく、なんでもできます。 キーポイントは、メソッドは、オブジェクトかクラスを背景に 呼び出されるということです。 たとえば、一つの特別なフィールドから値を持ってきたりセットしたりする 以上のことをするオブジェクトメソッドを示しましょう。
sub exclaim { my $self = shift; return sprintf "Hi, I'm %s, age %d, working with %s", $self->{NAME}, $self->{AGE}, join(", ", @{$self->{PEERS}}); }
または、次のようなもの:
sub happy_birthday { my $self = shift; return ++$self->{AGE}; }
次のような方法でやると言う人もいるでしょう:
sub exclaim { my $self = shift; return sprintf "Hi, I'm %s, age %d, working with %s", $self->name, $self->age, join(", ", $self->peers); }
sub happy_birthday { my $self = shift; return $self->age( $self->age() + 1 ); }
ですが、これらのメソッドはクラス自身ですべて実行できるので、このことは 重要ではないかもしれないません。 トレードオフがあります。 直接なハッシュのアクセスはそうでない場合より速く(実際のところ、 およそ桁違いに速く)、文字列に手を加えたい時には、より便利です。 ですが、メソッド(外部のインターフェース)を使うと、内部的に、 クラスを使う人だけでなく、クラスの作者自身が、データ表現に変更を 加えることの盾にもなります。
"クラスデータ" - クラスのそれぞれのオブジェクトに共通のデータアイテム - とは なんでしょう? 何のためにクラスデータが欲しいでしょう? たぶん、Person クラスでは、生きている人々の総数を追っておきたいでしょう。
$Person::Census と呼ばれるグローバル変数でそういうことが できます。 ですが、そのようにする唯一の理由は、人々が直接にクラスデータを 得られるようにしたい場合です。 $Person::Census と言うだけで、それをいじることが出来ます。 あなたの考えでは、そういったことは構わないのかもしれません。 ひょっとすると、変数が持ち出されることさえ、望んでいるかもしれません。 持ち出されるのであれば、(パッケージの)グローバル変数でなければなりません。 これがオブジェクト指向的なモジュールでなく、伝統的なモジュールであれば、 そうすればいいでしょう。
クラス変数をパッケージのグローバル変数にするやり方は、ほとんどの伝統的な モジュールで、期待されています。 ですが、このやり方は、一般的に、ほとんどのオブジェクト指向のモジュールでは、 むしろ下手なものと考えられています。 オブジェクト指向のモジュールでは、(訳註: データを)保護をするベールを設けて、 実装とインターフェースを分離します。 ですから、オブジェクトデータにアクセスするオブジェクトメソッドを 提供するのと同じように、クラスデータにアクセスするクラスメソッドを提供します。
それで、まだ、$Census をパッケージのグローバル変数とし続けることが、 できますし、そして、他人がモジュールの契約を支持し、そのために、 その実装をいじくり回さないと信頼することが できます。 かなりトリッキーに、perltieに記述されているように、$Census を、 tie されたオブジェクトにすることもできます。
ですが、たいていは、クラスデータをファイルスコープのレキシカル変数に したいと思います。 そうするためには、ファイルの上の方で、単純に次のものを置けばいいのです:
my $Census = 0;
my()
のスコープは、ふつう宣言されたブロックが終わったとき(この場合では、
全てのファイルが必要とされ、使われているとき)が期限ですが、Perl の
レキシカル変数の深い束縛は、そのスコープ内で宣言された関数に
アクセスできる間は、変数が割り当て解除されないことを保証します。
このことは、local() にて、一時的な値を与えられたグローバル変数では、
働きません。
$Census をパッケージのグローバル変数のままにするか、代わりに、 ファイルスコープのレキシカル変数にするかどうかに関わりなく、 Person::new() コンストラクタに、次のような変更をするべきです。
sub new { my $class = shift; my $self = {}; $Census++; $self->{NAME} = undef; $self->{AGE} = undef; $self->{PEERS} = []; bless ($self, $class); return $self; }
sub population { return $Census; }
こうしたことで、もちろん、Person が壊れたときに、$Census を減らすために、 デストラクタが必要になります。 次のようにします:
sub DESTROY { --$Census }
デストラクタ内で、割り当て解除するためのメモリがないことに、どのようにして 気づくのでしょう? そのことは、Perl 自身が面倒をみます。
代わりに、CPANから、Class::Data::Inheritable モジュールを使うことも出来ます。
クラスデータを絶えず取り扱うやり方は、本当によくないことがわかります。 十分に拡張的なルールは、オブジェクトメソッドから、直接にクラスデータを 参照しない ことです。 そうでなければ、拡張的な、継承できるクラスは作らないことです。 オブジェクトは、全てのオペレーションのために、ランデブーポイントに いなければなりません。 特に、とりわけオブジェクトメソッドからは。グローバル変数(クラスデータ)は、 ある意味で、派生クラスの中で、「間違った」パッケージにあります。 Perl では、メソッドはメソッドが定義されたクラス内のコンテキストで 実行するのであり、メソッドが動かされたオブジェクトの コンテキストでは ありません。 そのために、名前空間 - メソッド内で、目に見えるパッケージの グローバル変数の - は、継承と無関係です。
その名前空間を得られるでしょうか? たぶん、無理です。 よし、たとえば、他のクラスが、上で定義されているような(訳註:Person クラスの) DESTORY メソッドを、(たぶん継承されて)「借りた」としましょう。 これらの他のクラスのオブジェクトが破壊されたら、オリジナルの $Census 変数は 変化させられます。 新しいクラスのパッケージの名前空間の $Census ではありません。 ひょっとしたら、このことは、あなたの望むものかもしれませんが、十中八九 違うでしょう。
これを修正する方法があります。 ハッシュのキー "_CENSUS" で、アクセスされる値のデータにリファレンスを 蓄えます。 なぜアンダースコアをつけるかって? たぶん、たいてい、最初のアンダースコアは、いつも C プログラマに不思議な感覚を 伝えるからです。 このフィールドが、特殊であり、NAME や、AGE や PEERS と同じようなパブリックな データメンバではないということを思い出させる、単なる、記憶を助ける工夫です。 (このコードは、strict プラグマをつけて、開発しているので、 perl 5.004 以前なら、フィールド名をクォートしないといけないでしょう)
sub new { my $class = shift; my $self = {}; $self->{NAME} = undef; $self->{AGE} = undef; $self->{PEERS} = []; # "private" data $self->{"_CENSUS"} = \$Census; bless ($self, $class); ++ ${ $self->{"_CENSUS"} }; return $self; }
sub population { my $self = shift; if (ref $self) { return ${ $self->{"_CENSUS"} }; } else { return $Census; } }
sub DESTROY { my $self = shift; -- ${ $self->{"_CENSUS"} }; }
クラスにデバッグのメカニズムがあるのは、一般的です。
たとえば、オブジェクトが作られるか、破壊される時に、(訳註: デバッグ情報を)
見たいでしょう。
そうするためには、デバッグの値をファイルスコープのレキシカル変数に加えます。
このために、警告と致命的なメッセージを発する標準の Carp モジュールを
呼びましょう。
この方法により、自分自身のメーッセージの代わりに caller のファイル名と
行数と一緒に出ます;
もし、自分自身の観点から、警告や致命的なメッセージが欲しいのなら、
croak()
の代わりに、ただ、die() と warn()
を、直接に使いましょう。
use Carp; my $Debugging = 0;
さて、新しいクラスメソッドに、変数のアクセスを加えましょう。
sub debug { my $class = shift; if (ref $class) { confess "Class method called as object method" } unless (@_ == 1) { confess "usage: CLASSNAME->debug(level)" } $Debugging = shift; }
さて、DESTROY に手を入れて、消滅しかけているオブジェクトが失効する時に ちょっと文句を言うようにしましょう:
sub DESTROY { my $self = shift; if ($Debugging) { carp "Destroying $self " . $self->name } -- ${ $self->{"_CENSUS"} }; }
ひょっとしたら、オブジェクトごとに、デバッグ状態を持ちたいかも知れません。 両方ともできるを方法があります:
Person->debug(1); # entire class $him->debug(1); # just this object
こうするためには、デバッグメソッドは、「二頂の」メソッドで なければなりません。 クラス および オブジェクトの両方で動くメソッドです。 そのため、debug() と、DESTORY メソッドを次のように調整します:
sub debug { my $self = shift; confess "usage: thing->debug(level)" unless @_ == 1; my $level = shift; if (ref($self)) { $self->{"_DEBUG"} = $level; # just myself } else { $Debugging = $level; # whole class } }
sub DESTROY { my $self = shift; if ($Debugging || $self->{"_DEBUG"}) { carp "Destroying $self " . $self->name; } -- ${ $self->{"_CENSUS"} }; }
(Employee と呼ばれる)派生クラスが、Person 基底クラスからメソッドを
継承していると、何が起きるでしょうか?
Employee->debug()
は、クラスメソッドとして呼ばれた場合、
$Employee::Debugging ではなく、$Person::Debugging を操作します。
オブジェクトのデストラクタは、それぞれの別個のオブジェクトごとの死を 取り扱います。 しかし、クラス全体のシャットダウン - これは現在のところプログラムが 終了した時に起こる - ちょっとした掃除が欲しいときもあります。 そのような クラスのデストラクタ を作るために、 そのクラスパッケージの中に、END と名付けられる関数を作ります。 END の働きは、伝統的なモジュールの END 関数とちょうど似ています。 プログラムが実行されないか、捕らえられないシグナルで死んで、 プログラムが終了したときにいつでも呼ばれます。 例えば、
sub END { if ($Debugging) { print "All persons are going away now.\n"; } }
プログラムが終了しとき、全てのクラスデストラクタ(END 関数)は、 クラスがロードされたのと反対の順番(LIFO の順番)で呼ばれます。
そして、ドキュメントがあります: 今まで、この Person クラスの 実装 を 見せています。 Person クラスの インターフェース は、Person クラスのドキュメントに なります。 ふつう、このことは、同じファイルに、pod("plain old documentation") の フォーマットでドキュメントを置くことを意味します。 Person の例では、Person.pm ファイルの中に、下に続くドキュメントを配置します。 たとえ、このドキュメントがほとんどコードに見えても、違います。 これは、埋め込まれたドキュメントであり、pod2man や、pod2html や、 pod2text プログラムで使われます。 Perlのコンパイラは、podをまったく無視します。 ちょうど、翻訳者がコードを無視するのと同じです。 略式のインターフェースを記述する pod の例です:
=head1 NAME
Person - class to implement people
=head1 SYNOPSIS
use Person;
################# # class methods # ################# $ob = Person->new; $count = Person->population;
####################### # object data methods # #######################
### get versions ### $who = $ob->name; $years = $ob->age; @pals = $ob->peers;
### set versions ### $ob->name("Jason"); $ob->age(23); $ob->peers( "Norbert", "Rhys", "Phineas" );
######################## # other object methods # ########################
$phrase = $ob->exclaim; $ob->happy_birthday;
=head1 DESCRIPTION
The Person class implements dah dee dah dee dah....
インターフェース対実装の問題があります。 モジュールを開いて、インターフェースの契約の後ろにある、安全に鍵を かけられた、全てのプライベートなほのかに輝く小さなものを、 いじくりまわすようなプログラマには、保証が無効になります; 彼らの運命を気に病むべきではありません。
後になって、よりよい名前を実装するために、クラスを変えたくなったとしましょう。 たぶん、ファーストネーム(その人の宗教にかかわりなく、クリスチャンネームと 呼ばれる)とファミリーネーム(名字と呼ばれる)の両方と、それに加えて、 ニックネームと称号もサポートしたくなったとします。 もし、Person クラスを使う人が、このドキュメントに書かれたインターフェースを 通して、適切にアクセスしていれば、簡単にその下にある実装を変更出来ます。 もし、そうでなければ、Person クラスを使う人は損をします。 契約を破り、保証を失効したむくいです。
次のようにして、他のクラスを作ります。 このクラスは、Fullname というクラスです。 Fullname クラスとは、どのようなものでしょうか? その質問に答えるには、最初に、それをどのように使いたいかを 把握しなければなりません。 Fullname クラスは次のように使います:
$him = Person->new(); $him->fullname->title("St"); $him->fullname->christian("Thomas"); $him->fullname->surname("Aquinas"); $him->fullname->nickname("Tommy"); printf "His normal name is %s\n", $him->name; printf "But his real name is %s\n", $him->fullname->as_string;
よし。 こうするために、Person::new() をフルネームのフィールドをサポートするように、 次のように変えます:
sub new { my $class = shift; my $self = {}; $self->{FULLNAME} = Fullname->new(); $self->{AGE} = undef; $self->{PEERS} = []; $self->{"_CENSUS"} = \$Census; bless ($self, $class); ++ ${ $self->{"_CENSUS"} }; return $self; }
sub fullname { my $self = shift; return $self->{FULLNAME}; }
それから、古いコードをサポートするために、Person::name() を次のように 定義します:
sub name { my $self = shift; return $self->{FULLNAME}->nickname(@_) || $self->{FULLNAME}->christian(@_); }
これが Fullname クラスです。 データフィールドを持つのにハッシュリファレンスを使ったのと同じテクニックを 使い、データフィールドにアクセスするのに適切な名前でメソッドを使います:
package Fullname; use strict;
sub new { my $class = shift; my $self = { TITLE => undef, CHRISTIAN => undef, SURNAME => undef, NICK => undef, }; bless ($self, $class); return $self; }
sub christian { my $self = shift; if (@_) { $self->{CHRISTIAN} = shift } return $self->{CHRISTIAN}; }
sub surname { my $self = shift; if (@_) { $self->{SURNAME} = shift } return $self->{SURNAME}; }
sub nickname { my $self = shift; if (@_) { $self->{NICK} = shift } return $self->{NICK}; }
sub title { my $self = shift; if (@_) { $self->{TITLE} = shift } return $self->{TITLE}; }
sub as_string { my $self = shift; my $name = join(" ", @$self{'CHRISTIAN', 'SURNAME'}); if ($self->{TITLE}) { $name = $self->{TITLE} . " " . $name; } return $name; }
1;
最後に、テストプログラムです:
#!/usr/bin/perl -w use strict; use Person; sub END { show_census() }
sub show_census () { printf "Current population: %d\n", Person->population; }
Person->debug(1);
show_census();
my $him = Person->new();
$him->fullname->christian("Thomas"); $him->fullname->surname("Aquinas"); $him->fullname->nickname("Tommy"); $him->fullname->title("St"); $him->age(1);
printf "%s is really %s.\n", $him->name, $him->fullname->as_string; printf "%s's age: %d.\n", $him->name, $him->age; $him->happy_birthday; printf "%s's age: %d.\n", $him->name, $him->age;
show_census();
オブジェクト指向プログラミングのシステムは、全て、継承の考えをサポートします。 継承は、一つのクラスが他のクラスの上におんぶすることを意味します。 ですから、同じコードをくり返し、くり返し、書かなくて済みます。 つまり、ソフトウェアの再利用です。 そして、それゆえ、不精 - プログラマの重要な美徳 - に関連します。 (伝統的なモジュールの import/export メカニズムもまた、コードの再利用の形です。 ですが、オブジェクトモジュールに見られる、本当の継承よりも単純なものです)
継承の文法は、時に、言語の核に組み込まれていることがあれば、 そうでないときもあります。 Perlは、一つのクラス(や、複数のクラス)から、継承するのに特別な文法は ありません。 その代わりに、全て厳密に意味論にあります。 それぞれのパッケージには、@ISA と呼ばれる変数があり、@ISA は、 (メソッド)継承を治めるものです。 もし、オブジェクトかクラスで、メソッドを呼ぼうとして、そのメソッドが、 そのオブジェクトのパッケージには見付からないと、Perl は、見付からない メソッドを検索するさいに、@ISA を見て、他のパッケージを探しに行きます。
特別なパッケージ毎の、Exporter で認識される変数
(@EXPORT, @EXPORT_OK, @EXPORT_FAIL, %EXPORT_TAGS, $VERSION) のように、
@ISA 配列は、パッケージスコープのグローバル変数で なければならず、
my()
で作られるファイルスコープのレキシカル変数であっては いけません。
ほとんどのクラスで、@ISA 配列には一つのものしかありません。
このことは、「単一継承」("single inheritance")と呼ばれるものです。
省略して、SI とも呼ばれます。
このクラスについて、考えてみましょう:
package Employee; use Person; @ISA = ("Person"); 1;
多くないでしょ? 今のところ、この Employee クラスがするのは、他のクラスをロードすることと、 必要なら、その、他のクラスからメソッドを継承することを始めることだけです。 Employee クラスには、それ自身のメソッドを与えてはいません。 ですが、Employee クラスに、ちょうど Person クラスのような振る舞いを 当てにします。
このような空のクラスを作ることは、「空のサブクラステスト」と呼ばれます。 これは、基底クラスから継承する以外には、なにもしない派生クラスを 作ることです。 オリジナルの基底クラスは、適切に設計されていれば、 新しい派生クラスは、古いものの差し込み式の置換として、使われます。 このことが意味するのは、次のようにプログラムを書けるということです:
use Employee; my $empl = Employee->new(); $empl->name("Jason"); $empl->age(23); printf "%s is age %d.\n", $empl->name, $empl->age;
適切な設計によって、そうできます。
適切な設計とは、常に bless()
に、2 つの引数を与え、グローバルなデータに
直接アクセスするのを避け、何も export しない設計のことです。
上の方で定義している Person::new() 関数を見返したら、そうするように
注意しています。
コンストラクタで使われるパッケージデータが少しありますが、
パッケージデータへのリファレンスが、オブジェクト自身に蓄えられており、
他の全てのメソッドはそのリファレンスを通してパッケージデータに
アクセスします;
それで、よしとすべきです。
Person::new() 関数で、意図したこと -- 実際にはメソッドではないですか?
たぶん、原則的には、メソッドです。
メソッドは、ちょうど、その最初の引数に、クラスの名前(パッケージ)か、
オブジェクト(bless されたリファレンス)を期待する関数です。
Person::new()は、結局、Person->new()
メソッドと
Employee->new()
メソッドの両方が呼んでいる関数です。
メソッド呼び出しが関数呼び出しによく似ていながら、本当は、まったく
同じではなく、この 2 つを同じものとして扱うなら、すぐに壊れたプラグラムしか
残されないことを理解してください。
第一に、現実に、基礎となっている呼び出しの慣例が違っています:
メソッド呼び出しは、特別な引数を得ます。
第二に、関数呼び出しは継承をしませんが、メソッド呼び出しは継承をします。
メソッド呼出 結果として、関数呼び出し ----------- ------------------------ Person->new() Person::new("Person") Employee->new() Person::new("Employee")
ですので、メソッドを呼ぼうとしているときに関数呼び出しを使わないでください。
従業員がただの Person だったら、それはあまり面白くありません。 さあ、他のメソッドを追加しましょう。 従業員に、データフィールドを設けて、従業員の給与や、従業員 ID や、入社日に アクセスしましょう。
これらのオブジェクトのデータを取得するような、ほとんど同じメソッドたちを 作るのにちょっと飽きたとしても、落胆しないでください。 後で、こういった作業を短縮するためのいくつかの種々の便利な メカニズムについて説明します。 その逆に、単純なやりかたは次のものです:
sub salary { my $self = shift; if (@_) { $self->{SALARY} = shift } return $self->{SALARY}; }
sub id_number { my $self = shift; if (@_) { $self->{ID} = shift } return $self->{ID}; }
sub start_date { my $self = shift; if (@_) { $self->{START_DATE} = shift } return $self->{START_DATE}; }
派生クラスとその基底クラスの両方が同じ名前のメソッドを定義したら、 何が起きるでしょうか? たぶん、そのメソッドは派生クラスのもの(訳註:version)を得ます。 例えば、従業員で、peers()メソッドを呼ぶと、(訳註:Personとは、)少々違った 動きをさせたいとしましょう。 同僚の名前のリストをただ返す代わりに、ちょっと違った文字列を返しましょう。 次のようにします:
$empl->peers("Peter", "Paul", "Mary"); printf "His peers are: %s\n", join(", ", $empl->peers);
次のようになります:
His peers are: PEON=PETER, PEON=PAUL, PEON=MARY
こうするためには、単に、この定義を Employee.pm ファイルに加えるだけです:
sub peers { my $self = shift; if (@_) { @{ $self->{PEERS} } = @_ } return map { "PEON=\U$_" } @{ $self->{PEERS} }; }
ここで、ちょうど、多態性(polymorphism) として広く知られている、大げさな コンセプトのデモンストレーションをしています。 存在するオブジェクトの形と振るまいを担い、われわれ自身の目的に 適応するように、存在するオブジェクトを変更します。 これは、不精の形です。 (polymorph させることは、魔法使いが、あなたを、蛙に 見せようと決めるときに起こることです)。
しばしば、派生クラス("サブクラス"としても知られるクラス)のもの (訳註: version)とその基底クラス("スーパークラス"としても知られるクラス)の もの(訳註: version)も同じように両方をきっかけをメソッドに呼ばせたいでしょう。 実際に、コンストラクタとデストラクタは、そうすることを望んでいそうですし、 先にみた、debug() メソッドでは、十中八九、そうだとうなずけます。
そうするために、Employee.pm に次のものを加えます:
use Carp; my $Debugging = 0;
sub debug { my $self = shift; confess "usage: thing->debug(level)" unless @_ == 1; my $level = shift; if (ref($self)) { $self->{"_DEBUG"} = $level; } else { $Debugging = $level; # whole class } Person::debug($self, $Debugging); # don't really do this }
お分かりのように、(訳註: Employee から)向きを変えて、Person パッケージの
debug()
関数を呼びます。
ですが、良い設計にとっては、こうするのは、ひじょーに、壊れやすいものです。
Person が debug()
関数を持っておらず、その debug()
メソッドを
他のところから継承していたら、どうなるでしょう?
次のようにする方が良いです。
Person->debug($Debugging);
ですが、まだハードコードしすぎです。 次のようにする方が良いです。
$self->Person::debug($Debugging);
これは、Person 内で、debug() メソッドを探し始める楽しいやりかたです。 この作戦は、オーバーライドされたクラスメソッドよりも、 オーバーライドされたオブジェクトメソッドで、よく見ます。
少しのものがまだあります。 スーパークラスの名前をハードコードしています。 継承するクラスを変えるか、他のものを加えたら、このことは特に悪いです。 幸運なことに、仮名クラスの SUPER がここで助けになります。
$self->SUPER::debug($Debugging);
こうすると、自分のクラスの @ISA をのぞき始めます。
これは、メソッドコール 内 から意味をなすだけです。
他のどこからも、SUPER:: の何にも、アクセスしようとしないでください。
というのは、上書きされたメソッド呼出の外側には存在しないからです。
SUPER
は現在のパッケージのスーパークラスを参照するのであって、
$self
のスーパークラス ではない ことに注意してください。
事態はちょっと複雑になってきています。 してはならないことをしたでしょうか? これまでのように、まともなクラスかどうかをテストする一つの方法は、 空のサブクラステストによるものです。 すでに、チェックしている Employee クラスがあるので、 Employee から新しい空のサブクラスを生成できます。 次のものです:
package Boss; use Employee; # :-) @ISA = qw(Employee);
テストプログラムです:
#!/usr/bin/perl -w use strict; use Boss; Boss->debug(1);
my $boss = Boss->new();
$boss->fullname->title("Don"); $boss->fullname->surname("Pichon Alvarez"); $boss->fullname->christian("Federico Jesus"); $boss->fullname->nickname("Fred");
$boss->age(47); $boss->peers("Frank", "Felipe", "Faust");
printf "%s is age %d.\n", $boss->fullname->as_string, $boss->age; printf "His peers are: %s\n", join(", ", $boss->peers);
実行すると、まだ大丈夫です。 素敵なフォーマットで、デバッガで 'x' コマンドを働かせる方法のように オブジェクトをダンプしたいなら、CPAN から、Data::Dumpler モジュールを 次のように使うことが出来ます:
use Data::Dumper; print "Here's the boss:\n"; print Dumper($boss);
次のようなものを見せます:
Here's the boss: $VAR1 = bless( { _CENSUS => \1, FULLNAME => bless( { TITLE => 'Don', SURNAME => 'Pichon Alvarez', NICK => 'Fred', CHRISTIAN => 'Federico Jesus' }, 'Fullname' ), AGE => 47, PEERS => [ 'Frank', 'Felipe', 'Faust' ] }, 'Boss' );
ふーむ、何かがなくなっています。
給与、入社日、ID フィールドはどうでしょうか?
たぶん、それらに何も、undef さえもセットしていません。
そのため、ハッシュのキーにそれらが見えなかったのです。
Employee クラスは、それ自身の new()
メソッドがありません。
Person の new()
メソッドは、Employee について知りません。
(また、そうすべきでもありません: 適切なオブジェクト指向設計はサブクラスが
そのすぐ上のスーパークラスについて知っていてもよいと指示しています。
ですが、その逆は同様ではありません)。
では、Employee::new() を次のようにしましょう:
sub new { my $class = shift; my $self = $class->SUPER::new(); $self->{SALARY} = undef; $self->{ID} = undef; $self->{START_DATE} = undef; bless ($self, $class); # reconsecrate return $self; }
Employee か Boss オブジェクトをダンプしたら、今度は、新しいフィールドが そこに現れるでしょう。
よし、初心者もオブジェクト指向のエキスパートも混乱させる危険をおかしても、 白状する時がきました。 Perl のオブジェクトシステムは、多重継承、または、略してMI として知られる、 賛否両論の考えを含んでいます。 これが意味するのは、次のことで全てです。 ただ一つの親クラス - 今度は、それ自身が親クラスやそのほかを持っている - を 持つのではなく、2 つ以上の親クラスを持つことが出来ることです。 多重継承の使うことで、トラブルに見まわれることもありますが、 C++ のようなあいまいなオブジェクト指向言語の Perl では、うまくいけば、 たいした問題にはならないこともあります。
多重継承のやりかたは、実際、非常に簡単です: @ISA 配列に二つ以上の パッケージ名を置けばいいだけです。 Perl が、オブジェクトのために、メソッドを見つけに行く時がきたら、 順番に @ISA のパッケージをそれそれ見ます。 えっと、かなり。 完全に再帰的で、デフォルトでは深さ優先です(その他の順序解決手法については mro を参照してください)。 次のような @ISA 配列の組合せを考えてください:
@First::ISA = qw( Alpha ); @Second::ISA = qw( Beta ); @Third::ISA = qw( First Second );
Third クラスのオブジェクトがあるとすると:
my $ob = Third->new(); $ob->spin();
spin()
メソッド(または、さらに言えば、new())をどのように
見つけるのでしょうか?
検索は、深さ優先なので、クラスは次の順番で調べられます:
Third, First, Alpha, Second, Beta の順です。
実際には、多重継承が使われているクラスモジュールは、ほとんどみかけません。 ほとんどいつも、多重継承ではなく、一つのクラスは他の一つのクラスに 含まれているという単純なコンテナ関係を選びます。 これが、なぜ Person オブジェクトは、Fullname オブジェクトを 含んでいる かという理由です。 これは、Person オブジェクトが Fullname オブジェクトで あった と いうことを意味しません。
しかしながら、Perl での多重継承が見境のない、一つの特別な領域があります: 他のクラスのクラスメソッドを借りてくることです。 このことは、幾分普及しており、 バンドルされている「オブジェクトのない」クラスで特にそうです。 Exporter や、DynaLoader や、AutoLoader や、SelfLoader がそうです。 これらのクラスは、コンストラクタを提供しません。 これらのクラスは、(訳註: 他のクラスから)クラスメソッドを 継承するかもしれないので、存在しています。
たとえば、POSIX モジュールの @ISA は次のようになっています:
package POSIX; @ISA = qw(Exporter DynaLoader);
POSIX モジュールは、実際には、オブジェクトモジュールではありません。 ですが、Exporter も DynaLoader も、そうです。 Exporderと、DynaLoader クラスは、クラスメソッドの振るまいを POSIX に、 貸しているだけです。
なぜ、オブジェクトメソッドに、多重継承が使われないのでしょうか? 理由の一つは、複雑な副作用がありうるからです。 まず一つ例をあげると、継承のグラフ(ツリーではなく)が、同じ基底クラスに もどって集中します。 Perlは、再帰的な継承に対して防御していますが、単に、共通の祖先を通して、 お互いに関連している親を持つことは、近親相姦に思えても、禁止されていません。 もし、先ほど見た Third クラスで、new() メソッドが、Third クラスの 2 つの 親クラスで、オーバーライドされたコンストラクタを両方呼ぶとしたら、 どうなるんでしょう? SUPER 表記は、最初の一つしか見つけません。 Alpha と Beta クラスの両方が、共通の祖先、たとえば Nought クラスを 持っていたらどうでしょうか? オーバーライドされたメソッドを呼ぶ継承ツリーを登り続けるなら、結局、 Nought::new() を二度呼ぶことになります。 たぶん、それは、悪い考えでしょう。
全てのオブジェクトが、いくつかの根本的な基底クラスに根付いていたら、 便利でしょうか? すべてのオブジェクトに共通のメソッドを与えるのに、それぞれの @ISA に クラスを加えずにできる方法です。 それで、そうできることがわかります。 あなたはそれを見ることはありませんが、Perl は暗黙にかつ決定的に、 @ISA の終わりに特別な要素があることを想定しています: UNIVERSAL クラスです。 5.003 では、UNIVERSAL クラスにあらかじめ定義されているメソッドは ありませんでしたが、UNIVERSAL クラスにいれたいようなものは、なんでも 置くことが出来ました。
しかし、5.004 (または、5.003_08 のような、いくつかの破棄的なリリース)より、
UNIVERSAL には、既にいくつかのメソッドがあります。
これらのメソッドは Perl バイナリに組み込まれており、そのため、それらを
ロードするのに余計な時間はかかりません。
あらかじめ定義されているメソッドは、isa(), can()
, VERSION()
です。
isa()
はオブジェクトかクラスが、階層構造を横断することなく、
別のクラス「である」かどうかを教えます:
$has_io = $fd->isa("IO::Handle"); $itza_handle = IO::Socket->isa("IO::Handle");
can()
メソッドは、オブジェクトかクラスに対して呼ばれて、その文字列の引数が
そのクラスで、呼ぶことの出来るメソッドかどうかを帰って報告します。
実際、そのメソッドに関数のリファレンスを返します:
$his_print_method = $obj->can('as_string');
最後に、VERSION メソッドは、クラス(または、オブジェクトのクラス)が、 $VERSION と呼ばれるパッケージのグローバル変数が十分に高いかどうかを チェックします; 次のように:
Some_Module->VERSION(3.0); $his_vers = $ob->VERSION();
しかし、ふつう、VERSION を自分自身には呼びません。 (次のことを覚えておいてください。 すべての大文字の関数名は、Perl の慣例で、その関数が、なんらかの方法で、 Perl によって自動的に使われるだろうということを示しています。) このケースでは、次のようにすると同じことが起こります:
use Some_Module 3.0;
上に説明した Person クラスでバージョンのチェックを加えたいなら、 Person.pm に、次のことを加えるだけです:
our $VERSION = '1.1';
そして、Employee.pm で、次のようにできます:
use Person 1.1;
このことで、少なくともそのバージョン番号か、それより高いものが利用可能だと 確かめます。 正確なバージョン番号でのロードなので、同じではありません。 現在のところ、あるモジュールの複数のバージョンを同時にインストールする 機構はありません。 嘆かわしい。
(おそらくほとんどの場合で賢明ではないですが) @UNIVERSAL::ISA に他の パッケージ名を入れることもできます。 これらのパッケージも UNIVERSAL 自身と同様、全てのクラスによって 暗黙的に継承されます。 しかし、UNIVERSAL も @ISA 木によるその親も、全てのオブジェクトの明示的な 基底クラスにはなりません。 明確化するために、以下のコードを見てみます:
@UNIVERSAL::ISA = ('REALLYUNIVERSAL');
package REALLYUNIVERSAL; sub special_method { return "123" }
package Foo; sub normal_method { return "321" }
Foo->special_method()
を呼び出すと "123" を返しますが、
Foo->isa('REALLYUNIVERSAL') や Foo->isa('UNIVERSAL') を呼び出すと偽を
返します。
あなたのクラスが C3 のようなその他の mro を使っている場合 (mro を 参照してください)、UNIVERSAL / @UNIVERSAL::ISA 内でのメソッド解決は、 クラスの C3 mro が終了後、デフォルトの深さ優先左から右の方式で行われます。
上記の全ては、メソッド検索中に実際に何が起きるのかを実感することで より分かりやすくなります; これはおおよそ以下のような醜い擬似コードの ようになります:
get_mro(class) { # クラスの @ISA の開始から再帰して、適切な順序の # 検索する全てのクラスの単一の線形の配列を作成します。 # 使用されるメソッド解決順序 (mro) は、これにセットされている # mro「クラス」によります(デフォルト(深さ優先左から右)か # C3 順序のどちらかです)。 # このリストの最初の要素はこのクラス自身です。 }
find_method(class, methname) { foreach $class (get_mro(class)) { if($class->has_method(methname)) { return ref_to($class->$methname); } } foreach $class (get_mro(UNIVERSAL)) { if($class->has_method(methname)) { return ref_to($class->$methname); } } return undef; }
しかし、UNIVERSAL::isa を実装したコードは UNIVERSAL 自身では検索せず、 パッケージの実際の @ISA のみを検索します。
オブジェクトはハッシュリファレンスとして実装される必要はありません。 オブジェクトは、うまく bless されるリファレンスでありさえすれば、どんな 種類のリファレンスでも可能です。 このことは、スカラリファレンスでも、配列リファレンスでも、 コードのリファレンスでも、格好の的となるということです。
スカラは、オブジェクトがたった一つの値しか持たないなら、うまくいきます。 配列は、ほとんどのケースで、うまくいきます。 ですが、継承を少々危うくします。 派生クラスのために、新しい索引を作り上げなければならないからです。
クラスを使う人が、契約を支持し、公示されたインターフェースに我慢するなら、 その基礎を成すインターフェースを、そうしたければ変えることが出来ます。 これは同じインターフェースの仕様に合う、もう一つの実装です。 さて、オブジェクトを表現するのに、ハッシュリファレンスの代わりに、 配列リファレンスを使いましょう。
package Person; use strict;
my($NAME, $AGE, $PEERS) = ( 0 .. 2 );
############################################ ## オブジェクトコンストラクタ (配列版) ## ############################################ sub new { my $self = []; $self->[$NAME] = undef; # this is unnecessary $self->[$AGE] = undef; # as is this $self->[$PEERS] = []; # but this isn't, really bless($self); return $self; }
sub name { my $self = shift; if (@_) { $self->[$NAME] = shift } return $self->[$NAME]; }
sub age { my $self = shift; if (@_) { $self->[$AGE] = shift } return $self->[$AGE]; }
sub peers { my $self = shift; if (@_) { @{ $self->[$PEERS] } = @_ } return @{ $self->[$PEERS] }; }
1; # so the require or use succeeds
配列アクセスはハッシュアクセスより、だいぶ速いと思うかもしれません。 ですが、この2つは、実際は、類似のものです。 配列は、ちょっと 速いです。 ですが、10 とか、15 パーセントも速くはありません; たとえ、上の $AGE のような文字の変数を、1 のような数字に置き換えても、です。 二つの手法のより大きな違いは、メモリの使用で見つけられます。 ハッシュでの表現は、配列での表現よりもメモリを多く消費します。 値と同じように、キーにも、メモリを割り当てなければならないからです。 しかし、そのことは、本当に、そんなに悪いことではありません。 特に、5.004 からは、ハッシュにキーを与えるために、メモリは一度 割り当てられるだけです。 いくつのハッシュがキーを持つのかは問題ではありません。 今後、この 2 つの違いは、より効率的な基礎となる表現が工夫されるにつれ、 あいまいに消滅していくだろうと、期待されています。
まだ、スピード(と、メモリで、幾分大きくなるところ)で、ちょっと 優れていることは、プログラマに、単純なクラスのために配列表現を 選ばせるのに十分です。 やはり、拡張性にちょっとした問題があります; 後年、サブクラスを作ろうとと思ったときに、ハッシュがちょうどうまく 働くことがわかるでしょうから。
オブジェクトを表現するのに、コードリファレンスを使うことは、魅力ある将来性を 与えます。 世界中で唯一人しか、オブジェクトのデータを見ることが出来ない、 新しい匿名の(クロージャ)関数を作ることが出来ます。 というのは、データをレキシカルにしか見えない無名ハッシュに置くからです。 つまり、クロージャを作り、bless し、オブジェクトとして返すからです。 このオブジェクトメソッドは、一変して、普通のサブルーチン呼び出しとして クロージャを呼び、影響を与えたいフィールドに、それを渡します。 (はい、二重の関数呼び出しは、遅いです。 ですが、もし、速さを求めるのなら、オブジェクトを全く使わなければいいんです。 そうでしょ? :-)
使いかたは、前と似ています:
use Person; $him = Person->new(); $him->name("Jason"); $him->age(23); $him->peers( [ "Norbert", "Rhys", "Phineas" ] ); printf "%s is %d years old.\n", $him->name, $him->age; print "His peers are: ", join(", ", @{$him->peers}), "\n";
ですが、実装は、根本的に、たぶん、圧倒的なやりかたで、違っています:
package Person;
sub new { my $class = shift; my $self = { NAME => undef, AGE => undef, PEERS => [], }; my $closure = sub { my $field = shift; if (@_) { $self->{$field} = shift } return $self->{$field}; }; bless($closure, $class); return $closure; }
sub name { &{ $_[0] }("NAME", @_[ 1 .. $#_ ] ) } sub age { &{ $_[0] }("AGE", @_[ 1 .. $#_ ] ) } sub peers { &{ $_[0] }("PEERS", @_[ 1 .. $#_ ] ) }
1;
なぜなら、オブジェクトがコードリファレンスの背後に
隠されているからです。
このことはちょっとミステリアスです;
クロージャの由来する関数型プログラミング言語よりも、
標準的な手続き型や、オブジェクトベースのプログラミング言語の方に
よりしっかり根付いているからです。
new()
メソッドで作られて、返されたオブジェクトは、今まで見てきたような、
データのリファレンスではありません。
オブジェクトは、匿名のコードリファレンスであり、内部に、特定の
バージョン(レキシカルにバインドしており、インスタンス化している)の
- プライベートな変数 $self に蓄えられた - オブジェクトデータにアクセスする
コードを持っています。
それは、毎回、同じ関数であるにもかかわらず、違ったバージョンの $self を
含みます。
$him->name("Jason")
のようなメソッドが呼ばれると、暗黙の 0 番目の
引数がデフォルトとして呼び出されます -- 全てのメソッド呼び出しと同様です。
ですが、この場合では、その引数は先ほどのコードリファレンスです(C++ での
ポインタ関数のような何か、ですが、レキシカルな変数に深く
バインドされています)。
コードリファレンスを呼ぶこと以上に、コードリファレンスですることは
多くはありません。
つまり、このときするのは、&{$_[0]}
とすることだけです。
これは、単なる普通の関数呼び出しであり、メソッド呼び出しではありません。
最初の引数は、文字列 "NAME" であり、残りの引数は、メソッドに
渡されているもの全てです。
new()
で作られたクロージャの内側で実行されると、$self ハッシュリファレンスは
突然に見えるようになります。
クロージャは、その最初の引数(このケースでは、'NAME' です。
name()
メソッドがそれを渡したので)をつかんで、その文字を、ユニークな
バージョンの $self のプライベートで隠されたハッシュへの添字に使います。
この世で、誰も、実行されているメソッドの外側にいるものには、 この隠されたデータに近付けることを許されていません。 ええ、ほとんどありません。 デバッガを使っているプログラムを通すステップを選び、そのメソッドにいる間、 そのかけらを見つけることが できます。 ですが、他の全ての人には、運がありません。
もし、このことが Scheme なみなさんを興奮させなければ、何が 興奮させるのかわかりません。 このテクニックの、C++ や、Java や、他の脳死状態の静的な言語への翻訳が、 このキャンプの熱狂的なファンのくだらない課題としては、残っていますが。
caller()
関数で、ちょっとした詮索を加えることが出来ますし、クロージャが
それ自身のパッケージを呼ばない限り、動作を拒否させることも出来ます。
このことは、まちがいなく、プログラミングポリスと、その親類の厳格な人の、
ある種の潔癖な関心事を満足させるでしょう。
もしも、いつ、(プログラマの三大美徳である) 傲慢が開始するのかを疑問に 思っていたなら、ここにあります。 (もっとまじめにいうなら、傲慢は、ちょっとしたすばらしくよく設計された コードを書くことから生じる腕前のプライドです)。
オートロードは、未定義のメソッドの呼び出しをインターセプトする方法です。 オートロードルーチンは、その場で、新しい関数 - ディスクから ロードされるか、たぶん eval されているもの - を作ることを選ぶかもしれません。 この「その場で定義する」戦略が、オートローディングと呼ばれる理由です。
ですが、これはたった一つの可能な手法です。 もう一つの手法は、オートロードされたメソッドそれ自身に、直接、要求された サービスを提供させることです。 この方法を使ったとき、オートロードされたメソッドを「代理」メソッドのように、 考えるかもしれません。
Perlが特定のパッケージの未定義の関数を呼ぼうとし、その関数が
定義されていない場合、同じパッケージにある AUTOLOAD と呼ばれる関数を
探します。
もし、AUTOLOAD があれば、元の関数が持っていたのと同じ引数で、それが
呼ばれます。
その関数の完全修飾名は、パッケージのグローバル変数 $AUTOLOAD に
蓄えられます。
いったん呼ばれると、関数は、好きなように何かをすることが出来ます;
適当な名前で、新しい関数を定義すること、実際に装飾的な一種の、goto
を、
それに対して正しく行うこと、コールスタックからそれ自身を消すことを、
含みます。
これはオブジェクトと何が関係しますか? 結局、関数について話し続けているのであって、メソッドについてではありません。 たぶん、メソッドは、単に特別な引数と、それが見つけれらる場所について、 装飾的な記法を伴ったただの関数なので、オートローディングをメソッドとしても 使うことが出来ます。 Perl は、@ISA を通した再帰的な捜索に疲れ果てるまで、AUTOLOAD メソッドを 探し始めません。 どんな種類のオブジェクトに対する、未解決のメソッド呼び出しを トラップするために、UNIVERSAL::AUTOLOAD メソッドを定義する、 プログラマがいることも知られています。
最初に Person クラスと、それから、Employee クラスを見せた、はるか前に 遡ったら、二重化されたコードについて、おそらく、少し疑いを 持ち始めるでしょう。 それぞれのメソッドは、仮想的にまったく同じに見えるハッシュのフィールドに アクセスしていました。 このことは、偉大なプログラミングの美徳である、短気をくすぐります。 ですが、このときに、不精を勝たせて、なにもしません。 代理メソッドが、これを癒せます。
新しいデータフィールドをが欲しいと思うたびに新しい関数を毎回書く代わりに、
オートロードのメカニズムを使って、(実際に、偽の)メソッドをその場で
生み出します。
妥当なメンバにアクセスしていることを証明するために、
_permitted
("under-permitted" と発音します)フィールドでチェックします。
このフィールドは、%field と呼ばれるレコードの中の許されたフィールドの、
ファイルスコープのレキシカルな(C のファイル static のような)ハッシュ
リファレンスです。
なぜ、アンダースコアがあるのか?
かつて使った、_CENSUS フィールドと同じ理由のためです:
「内部的に使うためだけ」を意味するマークです。
ここにある、モジュールの初期化コードとクラスのコンストラクタは、 このアプローチを取る場合に見るようなものです:
package Person; use Carp; our $AUTOLOAD; # it's a package global
my %fields = ( name => undef, age => undef, peers => undef, );
sub new { my $class = shift; my $self = { _permitted => \%fields, %fields, }; bless $self, $class; return $self; }
もし、レコードに、デフォルトの値を持たせたいなら、現在の、%field ハッシュに
undef
のあるところに、それらを埋めることが出来ます。
オブジェクト自身に、クラスデータのリファレンスを保存させたやりかたに 気づいたでしょうか? 直接に %fields を参照するメソッドを持つか、そのほか、礼儀正しい継承を 持たない代わりに、オブジェクトを通してクラスデータにアクセスすることが 重要であることを思いだしてください。
けれども、本当の魔法は、代理メソッドにあります。 代理メソッドは、Person クラス(また、Person のサブクラス)のオブジェクトに 対する未定義のメソッドの全ての呼び出しを取り扱います。 AUTOLOAD と呼ばれなければなりません。 再び、これは全て大文字になります。 ユーザが直接呼ぶのではなく、Perl 自身によって、暗に呼ばれるからです。
sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object";
my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion
unless (exists $self->{_permitted}->{$name} ) { croak "Can't access `$name' field in class $type"; }
if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } }
とっても素敵でしょ? 新しいデータフィールドを加えるときにしなければならないことは、 %fields を変更することだけです。 新しい関数を書く必要はありません。
_parmitted
フィールドを、完全に避けることが出来ましたが、どのように
オブジェクトのクラスデータをリファレンスに蓄えるのかを実証したかったのです。
ですので、オブジェクトメソッドから、直接に、クラスデータにアクセスする
必要はありません。
しかし、継承ではどうなるのでしょう? Employeeクラスを同じように定義できるでしょうか? できます、十分に気をつける限りは。
気をつけ方です:
package Employee; use Person; use strict; our @ISA = qw(Person);
my %fields = ( id => undef, salary => undef, );
sub new { my $class = shift; my $self = $class->SUPER::new(); my($element); foreach $element (keys %fields) { $self->{_permitted}->{$element} = $fields{$element}; } @{$self}{keys %fields} = values %fields; return $self; }
これをしたら、Employee パッケージに、AUTOLOAD 関数を持つ必要はありません。 というのは、継承を通して、Person のバージョンを把握し、全て、 うまく動くからです。
代理メソッドが、データメソッドを関数としてくどくどコーディングするよりも、 より構造体風なクラスを作るのに、便利なアプローチを提供するといっても、 まだ、好ましいものが少し残されています。 第一には、代理メソッドを通してトラップさせるつもりのない、 いんちきの呼び出しを取り扱う必要があることを意味します。 上で詳細を述べたように、継承を扱う時に非常に慎重にならなければならないことも 意味します。
Perl プログラマは、さまざまなクラス生成クラスを作ることで、 これに対応しています。 これらのメタクラスは他のクラスを作るクラスです。 見るべき組は、Class::Struct と、Alias です。 これらと、他の関連するメタクラスは、CPAN のモジュールディレクトリで 見つけられます。
その古いものの一つは、Class::Struct です。 実際、その構文とインターフェースは、perl5 が実際のものに固まるだいぶ前に、 スケッチされました。 Class::Struct は、特定の型のフィールドがあるようなオブジェクトを持つクラスを 「宣言」する方法を提供します。 それが呼ばれる関数は、驚くようなものではなく、struct() です。 構造、またはレコードは、Perl では、基本の型ではないので、 レコード風なオブジェクトを提供するクラスを作るたびに、new() メソッドと、 それに加えて、別個に、レコードの各フィールドのために、 データアクセスメソッドを定義しなければなりません。 このプロセスに、すぐに飽きてしまうでしょう。 Class::Struct::struct 関数はこの退屈なことを緩和します。
これを使う簡単な例です:
use Class::Struct qw(struct); use Jobbie; # user-defined; see below
struct 'Fred' => { one => '$', many => '@', profession => 'Jobbie', # does not call Jobbie->new() };
$ob = Fred->new(profession => Jobbie->new()); $ob->one("hmmmm");
$ob->many(0, "here"); $ob->many(1, "you"); $ob->many(2, "go"); print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);
struct の中で、型を宣言でき、それは基本的な Perl の型になるか、 ユーザー定義の型(クラス)になります。 ユーザー型は、そのクラスのnew()メソッドを呼ぶことによって、初期化されます。
Jobbie
オブジェクトは Fred
クラスの new()
メソッドで自動的には
作成されないので、Fred
の実態を作成するときに Jobbie
オブジェクトを
指定することに注意してください。
struct の生成を使った実際の例があります。 たとえば、gethostbyname() と、gethostbyaddr() を、C 構造体のような振る舞いを するオブジェクトを返すように、オーバライドしたいとしましょう。 high-falutin' OO gunk は、気にしません。 やりたいことは、これらのオブジェクトが C 感覚で、構造体のような振る舞いを することです。
use Socket; use Net::hostent; $h = gethostbyname("perl.com"); # object return printf "perl.com's real name is %s, address %s\n", $h->name, inet_ntoa($h->addr);
Class::Struct モジュールを使ってこれをする方法です。 最重要ポイントは、この呼び出しです:
struct 'Net::hostent' => [ # note bracket name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ];
この呼び出しは、上記の名前と型のオブジェクトメソッドを作ります。
new()
メソッドさえも作ります。
次の方法でも、オブジェクトを実装できましたが、しませんでした:
struct 'Net::hostent' => { # note brace name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', };
こうすると、Class::Struct は、オブジェクト型を、無名配列の代わりに、 無名ハッシュを使います。 配列は、ハッシュより速く、小さいですが、ハッシュは、継承をしたいときに、 よく働きます。 この構造体風なオブジェクトには、継承を考えていないので、今回は、 よりよいスピードと、サイズに余裕をもたせることにしました。
実装は次のようになります:
package Net::hostent; use strict;
BEGIN { use Exporter (); our @EXPORT = qw(gethostbyname gethostbyaddr gethost); our @EXPORT_OK = qw( $h_name @h_aliases $h_addrtype $h_length @h_addr_list $h_addr ); our %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } our @EXPORT_OK;
# Class::Struct は、@ISA を使うのを禁止します sub import { goto &Exporter::import }
use Class::Struct qw(struct); struct 'Net::hostent' => [ name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ];
sub addr { shift->addr_list->[0] }
sub populate (@) { return unless @_; my $hob = new(); # Class::Struct made this! $h_name = $hob->[0] = $_[0]; @h_aliases = @{ $hob->[1] } = split ' ', $_[1]; $h_addrtype = $hob->[2] = $_[2]; $h_length = $hob->[3] = $_[3]; $h_addr = $_[4]; @h_addr_list = @{ $hob->[4] } = @_[ (4 .. $#_) ]; return $hob; }
sub gethostbyname ($) { populate(CORE::gethostbyname(shift)) }
sub gethostbyaddr ($;$) { my ($addr, $addrtype); $addr = shift; require Socket unless @_; $addrtype = @_ ? shift : Socket::AF_INET(); populate(CORE::gethostbyaddr($addr, $addrtype)) }
sub gethost($) { if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) { require Socket; &gethostbyaddr(Socket::inet_aton(shift)); } else { &gethostbyname; } }
1;
動的なクラスの作成に加えて、コアな関数のオーバーライドや、
import/export の例や、関数のプロトタイピングや、&whatever
を通しての
関数のショートカット呼び出しや、goto &whatever
での
関数の置き換えなどの他のコンセプトの正しい具体例を忍び込ませています。
これらの全ては、大半が、伝統的なモジュールの観点から意味のあるものです。
ですが、お分かりのように、オブジェクトモジュールとして、それらを使うことも
出来ます。
他の、オブジェクトベースのものや、File::stat、Net::hostent、Net::netent、 Net::protoent、Net::servent、Time::gmtime、Time::localtime、User::grent, User::pwent で、Perl の 5.004 リリースのコアな関数の構造体風な オーバーライドを見ることが出来ます。 これらのモジュールは、コンパイラのプログラムのためにとってある 慣例によって、全て小文字の最終的な構成要素を持っています。 これらが、コンパイルに影響し、組み込み関数を変更するからです。 これらは、C のプログラマがよく期待する型名を持っています。
C++ オブジェクトをよく使うなら、メソッド内からオブジェクトのデータメンバを 単純な変数のように得ることが出来るのになれているでしょう。 Alias モジュールは、これを提供します。 同時に、ちょっとだけよいのは、オブジェクトは呼ぶことが出来るけれど、 クラスの外側のみんなは呼ぶことの出来ない、 プライベートなメソッドのようなところです。
Alias モジュールをつかって Person を作る例です。 これらの魔術的なインスタンス変数を更新すると、自動的にハッシュの値の フィールドを更新します。 便利でしょ?
package Person;
# this is the same as before... sub new { my $class = shift; my $self = { NAME => undef, AGE => undef, PEERS => [], }; bless($self, $class); return $self; }
use Alias qw(attr); our ($NAME, $AGE, $PEERS);
sub name { my $self = attr shift; if (@_) { $NAME = shift; } return $NAME; }
sub age { my $self = attr shift; if (@_) { $AGE = shift; } return $AGE; }
sub peers { my $self = attr shift; if (@_) { @PEERS = @_; } return @PEERS; }
sub exclaim { my $self = attr shift; return sprintf "Hi, I'm %s, age %d, working with %s", $NAME, $AGE, join(", ", @PEERS); }
sub happy_birthday { my $self = attr shift; return ++$AGE; }
our
を宣言しなければならないのは、Alias がしているのが、
フィールドと同じ名前のパッケージのグローバル変数をいじれるようにするからです。
要するに、use strict
をしながら、グローバル変数を使うために、
これらを、先に宣言しなければなりません。
これらのパッケージの変数は、ちょうど、それらに、local() を使ったように、
attr()
呼び出しを囲んでいるブロックにローカライズされています。
しかし、このことは、ちょうど、他の local()
のように、まだ、一時的な値で、
グローバル変数をで考えていることを意味します。
Alias と、CLass::Structや、Class::MethodMaker を 混ぜて使うと良いでしょう。
さまざまなオブジェクト指向文献で、たくさんの違った言葉がほんの少ししか 違わない概念を表すのに使われているように思います。 もし、あなたがまだオブジェクト指向プログラマではないなら、 これらの、気まぐれな言葉に悩むことはありません。 ですが、もし、すでに、オブジェクト指向プログラマなら、Perl で、同じ コンセプトをどのように表すのか知りたいでしょう。
例えば、オブジェクトをクラスの インスタンス と呼ぶことや、それら インスタンスのオブジェクトメソッドを インスタンスメソッド と呼ぶことは、 一般的でしょう。 オブジェクトごとに特有のデータフィールドはよく、インスタンスデータ とか、 オブジェクト属性 と呼ばれ、全てのクラスのメンバに共通のデータフィールドは クラスデータ や、クラス属性 や、静的データメンバ と よく呼ばれるでしょう。
また、基底クラス や、一般クラス や スーパークラス など全てが、 同じ考えで記述します。 ですので、派生クラス や 特定クラス や、 サブクラス は、他の関連するものを記述します。
C++ プログラマには、静的メソッド や 仮想メソッド がありますが、 Perl プログラマには、クラスメソッド と、 オブジェクトメソッド しかありません。 メソッドが、クラス/オブジェクトメソッドとして使われるかどうかは、 使いかたによるだけです。 うっかりと、オブジェクト(リファレンスを期待するもの)で クラスメソッド(文字列の引数を期待するもの)を呼ぶことも出来ますし、 その逆も出来ます。
C++ の観点からは、Perl の全てのメソッドは、仮想的です。 ところで、このことは、メソッドが、普通の組み込み関数やユーザ定義関数が 出来るような、引数リストで関数のプロトタイプを、チェックされない理由です。
クラスは、それ自身、オブジェクトの何かですので、Perl のクラスは、 "メタオブジェクトとしてのクラス"(または、オブジェクト工場 とも 呼ばれます)哲学と、"型定義としてのクラス"(振る舞いを 宣言 し、 メカニズムを 定義しない)考えの両方を表すものとして見られます。 C++ は、後者をサポートしますが、前者は、サポートしません。
以下のマニュアルページは、間違いなく、このマニュアルページに多くの 背景を与えてくれます: perlmod, perlref, perlobj, perlbot, perltie, overload。
perlboot は、よりやさしい、オブジェクト指向プログラミングの 紹介です。
perltooc はクラスデータに関する詳細を提供します。
以下のモジュールは関心をひくでしょう: Class::Accessor, Class::Class, Class::Contract, Class::Data::Inheritable, Class::MethodMaker, Tie::SecureHash
Copyright (c) 1997, 1998 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.
Thanks to Larry Wall, Roderick Schertler, Gurusamy Sarathy, Dean Roehrich, Raphael Manfredi, Brent Halsey, Greg Bacon, Brad Appleton, and many others for their helpful comments.