Moose::Cookbook::Basics::Recipe2
- 簡単な銀行口座の例
概要
package BankAccount;
use Moose;
has 'balance' => (isa => 'Int', is => 'rw', default => 0);
sub deposit {
my ($self, $amount) = @_;
$self->balance($self->balance + $amount);
}
sub withdraw {
my ($self, $amount) = @_;
my $current_balance = $self->balance();
($current_balance >= $amount)
|| confess "Account overdrawn";
$self->balance($current_balance - $amount);
}
package CheckingAccount;
use Moose;
extends 'BankAccount';
has 'overdraft_account' => (isa => 'BankAccount', is => 'rw');
before 'withdraw' => sub {
my ($self, $amount) = @_;
my $overdraft_amount = $amount - $self->balance();
if ($self->overdraft_account && $overdraft_amount > 0) {
$self->overdraft_account->withdraw($overdraft_amount);
$self->deposit($overdraft_amount);
}
};
例によって、転写はここまで。
気になったところ
この例で気になったのは、extends でも before でもなくて、BankAccount の withdraw メソッドの中にある confess という関数。
文脈から warn とか die とか croak みたいなもんだろうな、とは想像がつくのだけれど、実際に実行してみないことには、、、ということで、実行してみる。
withdraw に balance 以上の値をわたして実行してみると、以下のような出力が返ってきた。
Account overdrawn at BankAccount.pm line 14
BankAccount::withdraw('BankAccount=HASH(0x9c9ef50)', 101) called at BankAccount.pm line 30
Class::MOP::Class:::before('CheckingAccount=HASH(0x982a6e8)', 151) called at /usr/lib/perl5/Class/MOP/Method/Wrapped.pm line 47
Class::MOP::Method::Wrapped::__ANON__('CheckingAccount=HASH(0x982a6e8)', 151) called at /usr/lib/perl5/Class/MOP/Method/Wrapped.pm line 89
CheckingAccount::withdraw('CheckingAccount=HASH(0x982a6e8)', 151) called at moose.pl line 13
ようするに、バックトレース付きの croak ってことですね。おそらく Moose.pm からexport されている関数だろうし、APIリファレンスを当たれば説明がでているのだろうけど、今回は放置。。。。
FOOTNOTES の (4) を読むと、''throw an error'' とあるので、やはり croak と同様に die を実行するみたい。
あと、上の概要に示されているソースプログラムをmainプログラムの末尾にくっつけて実行してみたら、
Can't locate object method "balance" via package "BankAccount" at moose.pl line 27.
といわれてエラーになってしまった。(''moose.pl line 27'' はちょうど ''$self->balance($self->balance + $amount'' の行)
BankAccount.pm って別ファイルに保存して use BankAccount; すれば正常に動作したので、なにかがマズかったんだろうけれど、なにがマズかったのかまでは未調査。
FOOTNOTES の (2) で クラスが使用される前にロードされていなければならない、みたいなことが書いてあるので、コンパイルされる順序の問題のようだ。
ようするに、該当するクラスを new する前に use するなり、定義してやるなりしないといけないということのよう。
その他のハイライト
- has のオプションで isa に 'Int', default に 0 を設定している (BankAccount の ''balance'' attribute)。コンストラクタに引数として渡されない限り、この初期値で初期化される。
- has のオプションで isa の値にクラス名が使える。(CheckingAccount の ''overdraft_account'')
- スーパークラスのメソッドの実行前に実行される ''before'' method modifier。 SUPER::method_name(args) を使ってもほぼ同じことが実現可能だが、スーパークラス側の引数が追加された場合などに追従するのが大変になる。
- isa=>'BankAccount' と指定されているときに'CheckingAccount'はBankAccountのサブクラスなので、代入可能である。(通常のオブジェクト指向と同じ)