多重継承しないほうがよい場合
前回は多重継承を利用してクラスを拡張するときにありがちな問題と、
たとえば
use strict;
use warnings;
use Test::More tests => 4;
package Mammal;
sub new { bless {}, shift; }
sub produce_milk { print "I can produce milk.\n"; }
package Bird;
sub new { bless {}, shift; }
sub fly { print "I can fly.\n"; }
package Bat;
use base qw(Mammal Bird);
package main;
my $bat = Bat->new;
can_ok($bat => 'produce_milk'); # ok
can_ok($bat => 'fly'); # ok
ok($bat->isa('Mammal')); # ok
ok($bat->isa('Bird')); # ok でも、コウモリは本当に鳥類ですか!?
このような多重継承によるメソッド拡張の問題をPerlの世界ではどう解消してきたのか。今回は、
Exporterを使った解決策とその問題点
さて、
Perl 5の場合、
use strict;
use warnings;
use Test::More tests => 4;
package Mammal;
sub new { bless {}, shift; }
sub produce_milk { print "I can produce milk.\n"; }
package Bird;
use base 'Exporter';
our @EXPORT_OK = qw(fly);
sub new { bless {}, shift; }
sub fly { print "I can fly.\n"; }
package Bat;
Bird->import('fly'); # use Bird 'fly';
use base 'Mammal';
package main;
my $bat = Bat->new;
can_ok($bat => 'produce_milk'); # ok
can_ok($bat => 'fly'); # ok
ok($bat->isa('Mammal')); # ok
ok(!$bat->isa('Bird')); # ok コウモリは鳥類ではなくなりました
そもそも鳥類の
でも、
package Bird;
use base 'Exporter';
our @EXPORT_OK = qw(fly);
sub new { bless {}, shift; }
sub fly_with { 'wings'; }
sub fly {
my $self = shift;
print "I can fly with ".$self->fly_with.".\n";
}
こう変えても、
このような欠点があることから、
Exporterモジュールとimportサブルーチンは通常のPerlにおいては重要であるが、オブジェクト指向Perlではほとんど使用されない。これは、クラスから変数またはサブルーチンをエクスポートすることがオブジェクト指向のカプセル化原理に反するためである。
山根ドキュメンテーション訳p.
Traits論文とその反響
ところが、
このトレート
メソッドを再利用するための工夫
従来のExporterを使った実装との違いはいくつかあるのですが、
だから、
たとえば、
package Fly;
use strict;
use warnings;
use Class::Trait 'base';
our @REQUIRES = qw(fly_with);
sub fly {
my $self = shift;
print "I can fly with ".$self->fly_with.".\n";
}
1;
そのFlyロールを利用するクラスの例はこうなります
package Bat;
use Class::Trait 'Fly';
use base 'Mammal';
sub fly_with { 'wings'; } # この行がなければコンパイルエラー
これだけだとポイントが見えづらいかもしれませんので、
package Aircraft;
use Class::Trait 'Fly';
use base 'Transportation';
sub fly_with { shift->engine_type; }
オブジェクトという
ロールに優先順位はありません
このように状態を持たないという性質から、
たとえば
package Dairying;
use strict;
use warnings;
use Class::Trait 'base';
our @REQUIRES = qw(animal);
sub produce_milk {
my $self = shift;
print "My ".$self->animal." produce milk.\n";
}
1;
このロールを利用して
use strict;
use warnings;
package DairyFarmer;
use base 'Mammal';
use Class::Trait (
Dairying => {
alias => { produce_milk => 'produce_cow_milk' },
exclude => 'produce_milk',
}
);
sub animal { 'cows'; }
package main;
my $farmer = DairyFarmer->new;
$farmer->produce_milk; # I can produce milk.
$farmer->produce_cow_milk; # My cows produce milk.
メソッドのつながりを知るためのメタモデル
このように、
もちろん従来のやり方でも、
だから
Perl 6そのものの書き方については割愛しますが、
use strict;
use warnings;
use Perl6::MetaModel;
use Perl6::Object;
role Fly => {
methods => {
fly => sub {
my $self = shift;
print "I can fly with ".$self->fly_with.".\n";
}
}
};
class Mammal => {
is => [ 'Perl6::Object' ],
instance => {
methods => {
produce_milk => sub { print "I can produce milk.\n"; }
}
}
};
class Bat => {
is => [ 'Mammal' ],
does => [ 'Fly' ],
instance => {
methods => {
fly_with => sub { 'wings' }
}
}
};
my $bat = Bat->new;
$bat->produce_milk;
$bat->fly;
Class::MOPとMooseの登場
ただ、
そのような試行錯誤に一段落がついて、
Mooseを使うと、
use strict;
use warnings;
use Test::More tests => 4;
package Fly;
use Moose::Role; requires 'fly_with';
sub fly {
my $self = shift;
print "I can fly with ".$self->fly_with.".\n";
}
package Mammal;
use Moose;
sub produce_milk { print "I can produce milk.\n"; }
package Bat;
use Moose; extends 'Mammal'; with 'Fly';
sub fly_with { 'wings'; }
package main;
my $bat = Bat->new;
can_ok($bat => 'produce_milk'); # ok
can_ok($bat => 'fly'); # ok
ok($bat->isa('Mammal')); # ok
ok(!$bat->isa('Bird')); # ok
このような小さな例ではありがたみがわかりづらいですが、
MooseはPerl 5とPerl 6の架け橋
ただし、
だから、
が、