PerlでCommandパターン


書いた後
ブログに認め
省みる

はろー!
顔だけがとりえの山下です。

そんなわけでコードを書いてはいちいちブログに書いて
自分の中に落としこむ作業が続いています。

でもこれって大事ですよね。
結構書くだけだとその時は炸裂的にわかってるんですけど
エンジニアって色々触るから次々に忘れちゃうんですよね。

そんなわけでCommandパターン。

Commandパターンは何らかの操作や要求そのものをオブジェクト化してしまって、
その先で何があるかわからないけども、とにかく操作や要求が出来てしまうという
状況を作れるようなパターンですね。

今回サンプルで書いたのはペーストするコマンド、削除するコマンドなのですが、
その要求をカプセル化してレシーバーとなるファイルやディレクトリに渡して
います。
ユーザーはペーストコマンドをファイルやディレクトリに渡すわけですが、
ファイルやディレクトリがどのようにペーストされるかは関知していません。

あとはコマンドそのものをオブジェクト化するのでどういったオペレーションが
行われたかを保存することができるので、Undo(やり直し)を実装できるという
利点があります。

コードはこのへんに
GitHub ka-yamashita

コード構成
・Client.pl
 ユーザーコード
・Command.pm
 コマンドクラス
・Invoker.pm
 コマンドを登録して実行するクラス
・Receiver.pm
 コマンド内容を実行するオブジェクトのクラス

イメージ的にはClientがCommandをInvokerに登録し、Invokerが実行命令すると
Receiverがせっせと処理を行う感じです。
この場合、何をやったかはInvokerが管理するのでUndoやRedoをやるのであれば、
Invokerに実行依頼をした処理を管理する仕組みが必要です。

ではCommandクラスから見て行きましょう。

[Perl]
use utf8;
binmode(STDOUT, “:utf8”);
#——————————–
# Name:Command.pm
#——————————–
package Command;
use Receiver;
use Moose::Role;
requires ‘execute’;
has ‘receiver’ => (
is => ‘rw’,
isa => ‘Receiver’,
);
no Moose::Role;

sub set_receiver {
my ($self,$receiver) = @_;
$self->receiver($receiver);
}

package PasteCommand;
use Moose;
with ‘Command’;
__PACKAGE__->meta->make_immutable();
no Moose;
sub execute {
my $self=shift;
$self->receiver->paste(“をペーストしました”);
}

package DeleteCommand;
use Moose;
with ‘Command’;
__PACKAGE__->meta->make_immutable();
no Moose;
sub execute {
my $self=shift;
$self->receiver->delete(“をデリートしました”);
}
1;
[/Perl]

コマンドクラスではPaste,Deleteのコマンドクラスが存在し、
各々set_receiverで設定されるレシーバーに対して、ペーストとデリートを要求する
という記述になっています。

次にその要求を受けるReceiverクラス

[Perl]
use utf8;
#——————————–
# Name:Receiver.pm
#——————————–

package Receiver;
use Moose::Role;
requires qw(paste delete);
no Moose::Role;

package DirReceiver;
use Moose;
with ‘Receiver’;
no Moose;
sub paste {
my $self=shift;
print “ディレクトリ:” . shift . “\n”;
}

sub delete {
my $self=shift;
print “ディレクトリ:” . shift . “\n”;
}
package FileReceiver;
use Moose;
with ‘Receiver’;
no Moose;
sub paste {
my $self=shift;
print “ファイル:” . shift . “\n”;
}

sub delete {
my $self=shift;
print “ファイル:” . shift . “\n”;
}
1;
[/Perl]
このクラスはディレクトリとファイルを意識したもので、paste,deleteメソッドが呼ばれると、
自身のオブジェクトを示す文字列と引数で受けた文字列をprintするだけのクラスです。
本来であればここにレシーバーごとの記述を書くことになります。
(例えばディレクトリであればdeleteは中のファイルも消すとか)

次にInvoker
[Perl]
use utf8;
#——————————–
# Name:Invoker.pm
#——————————–

package Invoker;
use Command;
use Moose;
use MooseX::AttributeHelpers;
has ‘commands’ => (
is => ‘rw’,
isa => ‘ArrayRef[Command]’,
metaclass => ‘Collection::Array’,
provides => {
push => ‘add_commands’
},
auto_deref => 1,
default => sub { [] }
);
__PACKAGE__->meta->make_immutable();
no Moose;
sub run {
my $self=shift;
for my $com($self->commands){
$com->execute();
};
};
1;
[/Perl]
このクラスはcommandsという配列変数にCommandオブジェクトを格納し、runメソッドが実行されると
commandsに格納されたCommandに対してexecuteメソッドを呼び出すだけです。
先にも書きましたが、Redo,Undoをやるのであればここで実行したことを
記憶する必要があります。

最後にClientコード

[Perl]
use utf8;
#——————————————-
# Name:Client.pl
#——————————————-
use Invoker;
use Command;
use Receiver;

#コマンドの集合となるインボーカー(実行者)
my $i=Invoker->new();

#コマンドのインスタンス化
my $comP=PasteCommand->new();
my $comD=DeleteCommand->new();

#コマンドの対象となるレシーバー
#このレシーバーに対してコマンドが実行される
#そしてレシーバーのみがコマンドの実行内容を知る
my $recD=DirReceiver->new();
my $recF=FileReceiver->new();

#ペーストコマンドにディレクトリのレシーバーをセットする
$comP->set_receiver($recD);

#デリートコマンドにファイルのレシーバーをセットする
$comD->set_receiver($recF);

#インボーカーに登録する
$i->add_commands($comP);
$i->add_commands($comD);

#実行する
$i->run();

[/Perl]

これはもうコメントの通りですね。
実行するとこんな感じになります。

[Shell]
[command]$perl Client.pl
ディレクトリ:をペーストしました
ファイル:をデリートしました
[command]$
[/Shell]

このパターンで設計することができれば、コマンド、レシーバーが増えた場合や、
またはその内容が変わった場合でも呼び出し元やInvokerは意識しないので、
完全に分離した構築を行うことが出来ます。

残るはFacade♥