http://lists.scsys.co.uk/pipermail/catalyst/2007-March/012769.html
YAPCでちょっとかぶるネタを喋るつもりなんだけど、これは時間の都合上これは含めない予定なので先にこっちに晒しておく。
NEXT.pmを使うとダイアモンド型のクラス継承構成の場合、
package Example::A;
package Example::B; @ISA = qw(Example::A);
package Example::C; @ISA = qw(Example::A);
package Example::D; @ISA = qw(Example::B Example::C);# 上記で全てのクラスに
# sub new { shift->NEXT::new(@_) }
# が定義されてると仮定
呼び出し順はD -> B -> A -> Cで、最後に呼ばれるべきベースクラスAがCより先に呼ばれる。もしA内でbless()を呼んでいるのであれば、
sub Example::A::new {
my $class = shift;
my $self = bless {}, $class;
$self->NEXT::new(@_); # あれ、ベースクラスなのにnew()ですか・・・
}sub Example::B::new {
my $class = shift;
my $self = $class->NEXT::new(@_);# 初期化
}sub Example::C::new {
my $class = shift;
# あれ、$class はすでにbless()されとる?
my $self = $class->NEXT::new(@_);# 初期化 ... ?
}
Bのコンストラクタは$_[0]にクラス名が、Cのコンストラクタには$_[0]にすでにbless()されたオブジェクトが入っている事になってしまう。一応この辺りもハックして順番が後になっても大丈夫なようにすることはもちろんできる
sub Example::C::new {
my $class = shift;
my $self = ref $class ? $class : $class->NEXT::new(@_);# 初期化 ...
}
でも、これなんか変。多分それぞれの中間のクラスがさらに複数の親を持っていたら場合によってはさらに変な事になってしまう可能性が高いし、こんな事に対応してまでNEXTのディスパッチングに合わせるっていうのがなんか嫌だ。一応自分の中の解決策としてはNEXTを使う場合はNEXTで呼び出すメソッド内の呼び出し順が問題にならないメソッドでやるべき。
# new()はベースクラスのみで実装
sub Example::A::new {
my $class = shift;
my $self = bless {}, $class;$self->initialize(@_);
}
sub Example::A::initialize {
my $self = shift;
# 全て共通の初期化
$self->NEXT::initialize(@_);
}sub Example::B::initialize {
my $self = shift;
$self->NEXT::initialize(@_);
# Bの初期化
}sub Example::C::initialize {
my $self = shift;
$self->NEXT::initialize(@_);
# Cの初期化
}sub Example::D::initialize {
my $self = shift;
$self->NEXT::initialize(@_);
# Dの初期化
}
ディスパッチの順番は守られないのは変わらないのだけれども、ディスパッチがAに来た時にNEXT::initialize()を呼ぶ前に初期化を行う事で最低限の初期化順番を守れる。
Class::C3かなぁ、やっぱり。