Moose::Cookbook::Recipe10 - role, requires, with -
Perl OOP に interface, abstract の概念を持ち込む role, requires, with の話です。
ソースコード
ちょっと変えてあります。
package Equivalent; use Moose::Role; requires 'equal_to'; sub not_equal_to { my ($self, $other) = @_; not $self->equial_to($other); } package Comparable; use Moose::Role; with 'Equivalent'; requires 'compare'; sub equal_to { my ($self, $other) = @_; $self->compare($other) == 0; } sub greater_than { my ($self, $other) = @_; $self->compare($other) == 1; } sub less_than { my ($self, $other) = @_; $self->compare($other) == -1; } package Printable; use Moose::Role; requires 'to_string'; package JP::Currency; use Moose; with 'Comparable', 'Printable'; has 'amount' => ( is => 'rw', isa => 'Num', default => 0 ); sub compare { my ($self, $other) = @_; $self->amount <=> $other->amount; } sub to_string { my $self = shift; sprintf('\%0.2f YEN', $self->amount); } package main; use Data::Dump qw(dump); use Perl6::Say; use Test::More qw(no_plan); eval { package InvalidEquivalentImpl; use Moose; with 'Equivalent'; }; if (my $err = $@) { ok($err, 'Not implements'); undef $@; } my @currencies = map { JP::Currency->new( amount => int(rand(10000)) + 1 ); } (0 .. 10); say dump (map { $_->amount } @currencies); my @sorted = sort { $a->compare($b) } @currencies; say dump (map { $_->amount } @sorted); for (my $i = 0; $i < @sorted; $i++) { if ($i > 0) { ok($sorted[$i]->greater_than($sorted[$i - 1]), 'greater than'); } ok($sorted[$i]->equal_to($sorted[$i]), 'equal_to'); say $sorted[$i]->to_string; if ($i < @sorted - 1) { ok($sorted[$i]->less_than($sorted[$i + 1]), 'less than'); } }
解説
use Moose::Role した package と requires
これは interface ないしは abstract class (実装も書ける点で) な役割を持ちます。
package Equivalent; ### この package が role であると言う宣言 use Moose::Role; ### equil_to メソッドを実装する事を期待する requires 'equal_to'; ### 実装されるであろう equal_to に依存するコードを Role に直接 impl sub not_equal_to { my ($self, $other) = @_; not $self->equial_to($other); }
はい、適宜コメントふりました。
public abstract class Equivalent { public abstract Boolean equal_to(Equivalent other) {} public Boolean not_equal_to(Equivalent other) { return this.equal_to(other); } }
with
package Comparable; use Moose::Role; with 'Equivalent'; requires 'compare'; sub equal_to { my ($self, $other) = @_; $self->compare($other) == 0; }
再び Comparable role を作るんですが、その際に Equivalent を実装する事も可能です。role を実装するぜって宣言が with になると。
ちなみに不正な実装した場合
eval { package InvalidEquivalentImpl; use Moose; with 'Equivalent'; }; if (my $err = $@) { ok($err, 'Not implements'); undef $@; }
Equivalent は requires 'equal_to' を実装する事を期待してるので、これはダメです。
こういうコードを書いた場合は実行時に即座にエラーとなります。
これが例えばインスタンス化した時にエラーになるんではやりづらいので、こうした定義時にエラー検出してくれるのは非常に嬉しいですね。
まとめ
role かわゆす。
*1:と言うか構文的に誤りありそうだおw