再びTraits: Composable Units of Behaviorのネタです。
Moose::Roleを使った場合、メソッドの優先順位は 自クラス → Role → スーパークラス ... となりますが、ここで Role だけは複数持てるため、競合がありえます。
以下のTaxとTotalの二つのRoleは、どちらもtotal_priceを持っています。
# 価格から税金計算 package Tax; use Moose::Role requires 'price'; sub tax { my $self = shift; return $self->price * 0.05; } sub total_price { my $self = shift; return $self->price + $self->tax; } no Moose::Role; # 単価から合計計算 package Total; use Moose::Role requires 'price', 'number'; sub total_price { my $self = shift; return $self->price * $self->number; } no Moose::Role;
この二つのRoleを合成して、Reciptクラスを作るとどうなるでしょう? *1
BEGIN { package Recipt; use Moose; with 'Total', 'Tax'; has 'price' => ( isa => 'Int', is => 'ro', required => 1, ); has 'number' => ( isa => 'Int', is => 'ro', required => 1, ); no Moose; }
'Total|Tax' requires the method 'total_price' to be implemented by 'Recipt' at XXXX.pm line YY
怒られました。ここでは以下の2点のことに注目して下さい。
- 「メソッドが競合している」ではなく、「クラス側にメソッドを実装しなさい」と怒られる
- しかもtotal_priceはrequiresしてるわけじゃないのでちょっとわかりにくい
- requiresでpriceが重複してるが怒られない(これはTraitsでは当然の仕様)
この競合の解決法として、aliasとexcludesが用意されてます。Reciptのwithを以下のように書き換えます。
with 'Total', 'Tax' => { alias => {total_price => 'tax_total'}, excludes => ['total_price'], };
これでエラーが出なくなりました。*2
または、言われるが侭にクラス側にtotal_priceメソッドを定義してもエラーは止まります。これは最初に書いたように、クラスのメソッドはRoleのメソッドより優先されるため、呼ぶべきメソッドが一意に定まるようになるためです。
詳しいことは、Moose::Cookbook::Roles::Recipe2や t/030_roles/005_role_conflict_detection.t とか見れば出ています。