Perl 5のOOは、慣れてしまうと簡単だ。
継承とか考えずに、普通にクラスを作りたければ、必要なのは以下の二行だけ。
package Klass; sub new { bless {} };
これだけでは何もできないので、とりあえずnameというアクセサーを追加してみる。これだけ。
sub name { my $self = shift; $self->{name} = shift if @_; return $self->{name}; }
しかし、上の形式だと、継承をサポートしていない。だから、
package Klass; sub new { bless {} }; package Klass::Sub; our @ISA = qw/Klass/; # new は継承 sub name { my $self = shift; $self->{name} = shift if @_; return $self->{name}; } package main; my $o = Klass::Sub->new(); $o->name("foo"); warn $o->name
としても、Can't locate object method "name" via package "Klass"
と怒られてしまう。
継承をサポートするには、bless()
にもう一つ引数を渡す。
package Klass; sub new { my $class = shift; bless {}, $class; }
この第二引数はクラスの名前。Klass::Sub->new
が呼ばれると、まずKlass::Sub::new()
が呼ばれるが、これは存在しないので、スーパークラスのKlass::new()
が呼ばれる。しかしこの時の第一引数は、当初の'Klass::Sub'
のままだ。ゆえにbless {}, 'Klass::Sub'
が実行され、スーパークラスのnew()
で、サブクラスのオブジェクトがきちんと生成される。
しかし、時にはClass->new
ではなく、$object->new()
で既存のオブジェクトを生成したいときもある。実はPerl Best PracitecsにてDamian Conwayは$object->new()
を戒めているが、ここではDCONWAY先生のありがたい教えに背いて、あえて両方できるようにしてみる。
sub new { my $thing = shift; my $class = ref $thing || $thing; bless {}, $class; }
これで、$object
と同じクラスのオブジェクトを作るnew()
が出来た。
しかし、どうせなら初期化も一緒にやりたい。どうすればよいか?こうすればよい。
sub new { my $thing = shift; my $class = ref $thing || $thing; bless { @_ }, $class; }
これで、Klass->new( name => 'foobar' )
で初期化も行える。
しかし、デフォルト値を持たせたければどうすればよいか、こうすればよい。
my %default = (name => 'anonymous'); sub new { my $thing = shift; my $class = ref $thing || $thing; bless { %default, @_ }, $class; }
しかし、要求はさらに続く。初期化してから、さらにスーパクラスの初期化ルーチンも呼びたい。どうすればよいか。こうすればよい。
package Klass; sub new { my $thing = shift; my $class = ref $thing || $thing; my $self = bless { @_ } => $class; $self->init() if $self->can('init'); $self; } our %default = (name => 'anonymous'); sub init { my $self = shift; $self->{$_} = $default{$_} for keys %default; $self; } package Klass::Sub; our @ISA = qw/Klass/; our %default = ('名前' => 'ななし'); sub init { my $self = shift; $self->{$_} = $default{$_} for keys %default; $self->SUPER::init(); }
newはベースクラスのそれを使い回し、initは継承チェーンを遡るというわけである。;p>
しかし、ここまでくれば、こうしたくはならないだろうか。
package Klass; sub new { my $thing = shift; my $class = ref $thing || $thing; my $self = bless { @_ } => $class; $self->init() if $self->can('init'); $self; } our %default = (name => 'anonymous'); sub init { my $self = shift; $self->{$_} = $default{$_} for keys %default; $self->SUPER::init() if $self->can('SUPER::init'); $self; } package Klass::Sub; our @ISA = qw/Klass/; our %default = ('名前' => 'ななし'); *init = \&Klass::init;
やってみればわかるが、これはうまく行かない。ここで&Klass::Sub::init
は独自の存在ではなく&Klass::init
の単なる別名だからだ。この場合、最初に親クラスの&Klass::init
が呼ばれたのと同じことになる。これは、init()
の役割を考えれば当然だろう。
実はここまでは前振り。ここからが面白くなるのだが次のEntryへ譲る。前ふりなのだが、意外と使えそうだったので別記事にしてみた次第。
Dan the Fiftiesh Thirty Something
けど、PHP5で書いていると、「myが使いたい!」「next unless... と書きたい!」「mapが使いたい」って欲求不満になりますね。Perl6に期待していますが、今まで作成したコードの移植を考えると萎えますね。
ところで、Perlではパッケージで変数宣言するとパッケージ変数(クラス変数)になりますが、PHPだとメンバー変数(アトリビュート/インスタンス変数)になります。その辺がまだよくわかっていないです。