AOPが騒がれ始めた辺りでJavaを辞めたので、AOPをよくわかってない。
で、AOPに関する記事を斜め読みしてたら「Separation of Concerns」ってのと「Crosscutting Concern」ってのが出てきたのでMooseでやってみた。
良くありがちな銀行口座クラスをMooseで作ってみる。
package BankAccount;
use Moose;
has 'balance' => (is => 'rw', isa => 'Int', default => 0);
sub deposit {
my ( $self, $amount ) = @_;
$self->balance( $self->balance + $amount );
}
sub withdraw {
my ( $self, $amount ) = @_;
$self->balance >= $amount
? $self->balance( $self->balance - $amount )
: confess 'account overdrawn';
1;
基本的なMooseによるクラス定義。一応説明しておくと
has 'balance' => (is => 'rw', isa => 'Int', default => 0);
balance(残高)という属性を持っていて$self->balanceがgetter。
$self->balance($amount)がsetter。
型チェックが行われてIntのみ受け付ける。
デフォルトはは0。
メソッドは二つで、depositが入金でwithdrawが出金。
これは普通のPerl OOのメソッドと同じ。
このクラスは普通に
my $account = BankAccount->new; warn $account->balance; $account->deposit(10000); warn $account->balance;
みたいに使えて結果は
0 10000
で、ここに要件として
「depositの後にいくら入金されたかログとして表示しなさい」
ってのが追加されたとする。
普通は
sub deposit {
my ( $self, $amount ) = @_;
$self->balance( $self->balance + $amount );
warn "log: deposited $amount ";
}
とかやるんだけど、これだと
1. 本質的な処理(ここで言うと入金された金額を残高に足す)って処理に本来関係ない処理(いくら入金されたか表示する)が含まれて見通し悪い。
2. 更に、ロギングとかって他にも使い道あるんだからまとめたい。
1.のようにコードの関心事を切り分けることをAOPでは「関心事の分離(Separation of Concerns)」っていう。
2.のようにいくつものクラスにまたがって利用される関心事をAOPでは「横断的な関心事(Crosscutting Concern)」っていう。
これをMooseで実装するにはどうするかというとMoose::Roleを使う。RoleはJavaで言うとこのInterfaceに実装を加えられるようなもの。
package BankAccount::Role::Logable;
use Moose::Role;
requires qw(deposit withdraw);
after 'deposit' => sub {
my ($self, $amount) = @_;
warn "log: deposited $amount ";
};
1;
requires qw(deposit withdraw);
は、このRoleを使うにはdepositメソッドととwithdrawメソッドを実装する必要がある、という意味で、
で、
after 'deposit' => sub {...
は、depositメソッドを呼ばれた後に以下のコードを実行するって意味。
で、
package BankAccount;
use Moose;
with 'BankAccount::Role::Logable';
has 'balance' => (is => 'rw', isa => 'Int', default => 0);
sub deposit {
my ( $self, $amount ) = @_;
$self->balance( $self->balance + $amount );
}
sub withdraw {
my ( $self, $amount ) = @_;
$self->balance >= $amount
? $self->balance( $self->balance - $amount )
: confess 'account overdrawn';
1;
ってする。
with 'BankAccount::Role::Logable';
は、BankAccount::Role::Logableを使うよっていう宣言。Javaで言うとimplementsみたいなもん。これをすることによってRoleで宣言されているメソッドを実装する必要が出てくる。
で、さっきのを実行すると
0 10000 log: deposited 10000
って見事ログが表示されてdepositメソッドを変更することなく要件は満たした。
つまり関心事を分離したことによって本来の関心事(Core Concern)に変更を加えることなく新たな関心事を付け加えることが出来るのでコードが見やすい。
ちなみにafterの他に当然beforeもあって、もう一つaroundってのもある。
「出金の前後にログを表示しなさい」
ってな要件が加わったらBankAccount::Role::Loggableに
around 'withdraw' => sub {
my ($next, $self, $amount) = @_;
print "log: before withdraw $amount:\n";
$next->($amount);
print "log: after withdraw $amount: \n";
}
的なことを加えればいい。BankAccountには何も変更なし。
ちょっといい例が思いつかないけど、同じようにBankAccount::Role::Loggableをwithしてdepositとwithdrawを実装したら同じようにログを吐いてくれる。
とにかく、こーゆーことをすることによってコードは見やすくなり、クラスの再利用しやすくなる…って理解してる。

コメントする