PerlでCommandパターン

Receiverクラスが実装。ReceiverクラスをCommandクラスでラップして実装をカプセル化。CommandクラスはAdapterパターン的に利用する。

ContextからRecieverを分離するため、Invokerを通じてCommandオブジェクトを呼び出す。下のプログラムにはないけど、InvokerへのCommandのセットをInvokerFactory的なクラスにまかせると、Contextの分離がよりハッキリする。

use strict;
use warnings;
use Data::Dumper;

my $invoker = new Invoker();

my $fireReceiver = new Receiver("fire!");
my $fireCommand = new ConcreteCommand($fireReceiver);
$invoker->setCommand($fireCommand);

my $shockReceiver = new Receiver("shock!");
my $shockCommand = new ConcreteCommand($shockReceiver);
$invoker->setCommand($shockCommand);

my $godReceiver = new Receiver("oh my god!");
my $godCommand = new ConcreteCommand($godReceiver);
$invoker->setCommand($godCommand);

$invoker->invoke(0);
$invoker->invoke(1);
$invoker->invoke(2);

#print Dumper($invoker);

{
 package Receiver;
 
 sub new{
  my $this = shift;
  my $msg = shift;
  return bless { msg => $msg, @_ }, $this;
 }
 
 sub action{
  my $this = shift;
  my $msg = $this->{msg};
  print "$msg @ Receiver\n";
 }
}

{
 package Command;
 
 sub new{
  my $this = shift;
  return bless { @_ }, $this;
 }
 
 sub invoke{
  die("need overriding\n");
 }
 
 sub undo{
  die("need overriding\n");
 }
 
 sub redo{
  die("need overriding\n");
 }
}

{
 package ConcreteCommand;
 
 sub new{
  my $this = shift;
  my $receiver = shift;
  my $obj = new Command(receiver => $receiver, @_);
  return bless $obj, $this;
 }
 
 sub invoke{
  my $this = shift;
  $this->{receiver}->action();
 }
 
 sub undo{
  die("need overriding\n");
 }
 
 sub redo{
  die("need overriding\n");
 }
}

{
 package Invoker;
 
 sub new{
  my $this = shift;
  my $commands = [];
  my $obj = {
   commands => $commands
  };
  return bless $obj, $this;
 }
 
 sub setCommand{
  my ($this, $command) = @_;
  push(@{$this->{commands}}, $command);
 }
 
 sub invoke{
  my ($this, $index) = @_;
  ${$this->{commands}}[$index]->invoke();
 }
}

undo, redoを実装したい場合は

  • Invoker, Commandにundo, redoインターフェースを追加
  • Commandに編集時のデータを参照できるようにしておく

参考
Undo,Redoの実装って何十回もやってる気がする