2007年04月23日 22:45 [Edit]

perl - 万能なnewの書き方

camel

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は継承チェーンを遡るというわけである。

しかし、ここまでくれば、こうしたくはならないだろうか。

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


この記事へのトラックバックURL

この記事へのトラックバック
というわけで、本番。 404 Blog Not Found:perl - 万能なnewの書き方ここからが面白くなるのだが次のEntryへ譲る
perl - POO と goto【404 Blog Not Found】at 2007年04月24日 00:49
この記事へのコメント
世の中の流れは、良くも悪くもOO=Javaで、OOの解説書の例題にはたいていJavaが使われています。そのため、私にとってPHP5は「型指定が不要なPerl + 正統派OOのJava」みたいな感じで、一番分かりやすかったです。Rubyだと文法を一からマスターしなければならないし。個人的にはDirecterのLingo(古い!)が一番分かりやすいOOだと思います。

けど、PHP5で書いていると、「myが使いたい!」「next unless... と書きたい!」「mapが使いたい」って欲求不満になりますね。Perl6に期待していますが、今まで作成したコードの移植を考えると萎えますね。

ところで、Perlではパッケージで変数宣言するとパッケージ変数(クラス変数)になりますが、PHPだとメンバー変数(アトリビュート/インスタンス変数)になります。その辺がまだよくわかっていないです。
Posted by Weasel at 2007年04月25日 00:20
何と言ってもperlのOOは後付ですよね。それでも出来る、ていうのがperlの柔軟性なのは間違いないでしょうが、非常にトリッキーな記述の嵐になっているような気がする素人です。packageおよびシンボルテーブルとリファレンスの奇跡的な組み合わせ、でしょうか(→書いておいて理解できません。)

JavaScriptのOOもまた別のパラダイムでの記述のようで、私には馴染みにくいです。
PHP5みたいにJavaコピー言語ばかりになっても仕方ないとは思うんですが、やはり自分に対するJavaの影響力のでかさを感じます。Rubyは入り口がシンプルでいいなあみたいな。
Posted by key at 2007年04月24日 22:46
コメントありがとうございます。

> 下から2番目のコード例で、
> my $k = Klass->new( name => 'foobar' );
> などとしたとき、nameが初期化されずにdefault値になると思います。

なるほど。自分はコンストラクタでメンバー変数を指定するのが何となくきらいで、

my $k = new Klass;
$k->setConfig( name => 'foobar' );

としているので、影響はないと思います。

あと、use Klass; の指定は、KlassとKlass::Subが別ファイルなら必要になる、ということに気づきました。

# 個人的にはPHP5のOOのほうが“素直”な気がします。
# Public / Private / Protected も使えるし。

Posted by Weasel at 2007年04月24日 11:02
下から2番目のコード例で、
my $k = Klass->new( name => 'foobar' );
などとしたとき、nameが初期化されずにdefault値になると思います。
Posted by foobar at 2007年04月24日 09:05
質問です。

> package Klass::Sub;
> our @ISA = qw/Klass/;

自分は@ISA設定前にuse Klass;をしていますが、これでも大丈夫なのでしょうか?

あと、PHPでオブジェクト指向を覚えたので、Klass::Sub->new ではなくて new Klass::Sub();としますが、大丈夫ですよね?

いろんなやり方がある、といっても、人と違う生き方に不安を覚えるので。
Posted by Weasel at 2007年04月24日 00:15