関数を直接継承するのではなく、クラス変数、そして関数のパラメータ となる関数を継承する・させることで、設計思想や、インターフェースを残しながら、 大きく拡張することが可能になる。
以下に自身のvalueを出力する3つの例を示す。
- 直に継承してオーバーライドする場合(拡張性小)
- 直接、to_valuesがオーバーライドされる
sub to_values {
my $self = shift;
return values %$self;
}
- クラス変数をオーバーライドする場合(拡張性中)
- クラスデータをオーバーライドすれば、拡張は出来るが、定められた形でしか出せず、いざとなれば、to_valuesがオーバーライドされる。
- keys_for_valuesは直接関数で定義しても良い。
use parent qw(Class::Data::Inheritable);
__PACKAGE__->mk_classdata(keys_for_values => [qw/key1 key2 key3/]);
sub to_values {
my $self = shift;
return map { $self->{$_} } @{$self->keys_for_values()};
}
- 関数をオーバーライドする場合(拡張性大)
- a, b, cの変更や、funcname_for_valuesの中身を変更するだけで良く、重要なI/Fであるto_valuesが変更されることは殆どない
sub funcname_for_values {
qw/a b c/;
}
sub a { my $self = shift; $self->{key1} }
sub b { my $self = shift; $self->{key2} }
sub c { my $self = shift; $self->{key3} }
sub to_values {
my $self = shift;
return map { $self->$_() } @{$self->funcname_for_hashref()};
}
- 継承による拡張では、ポリモーフィズムを実現するために、オブジェクトのType分のクラスが必要になる。
- 一方、ジェネリックによる拡張(C#では、宣言時に指定)を行うと、クラスがオブジェクトの数分必要になることはない。
- Perlはオブジェクトの型を実行時まで認識せず扱えるため、宣言時のType指定で制限を行うようにすると良いと思う。
my $string = String->new('aiueo');
my $int = Int->new(348);
my $obj = Obj->new('Int'); # 継承による拡張では、ObjIntとObjStringを別々のクラスとして実装する必要がある。
$obj->add($int); # 成功
$obj->add($string); # ここでdieする
package Obj;
sub new {
my ($class, $type) = @_;
bless +{
type => $type,
array => +[],
}, $class;
}
sub add {
my ($self, @values) = @_;
die unless $_->isa($self->{type}) for @values;
push @{$self->{array}}, $_ for @values;
}