読者です 読者をやめる 読者になる 読者になる

Commandパターン

Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)の例をPythonに変換して書いています。
目次


新しいGUIフレームワークを作っているとします。

ボタンが押されたとき、何かのタスク(ビープ音を鳴らす、メールの送信、ファイルの保存etc)
を実行しなければなりませんが、それをどうやって指定すればよいのでしょうか?

継承を使うのも一つの手です。TemplateMethodパターンです。

しかし、微妙に違うボタン毎にクラスを作りたくはありません。(200Hzの音を鳴らすボタン、220Hzの音を鳴らすボタン、240Hzの・・・)そこで「ボタンが押されたときの動作」を表現するクラスを作ります。

Commandパターンです。

class SlickButton(object):
    def __init__(self, command):
        self.command = command
    
    #描画等の処理のコード・・・
    
    def on_button_push(self):
        self.command.execute()

class BeepCommand(object):
    def execute():
        #音を鳴らす処理・・・

class SendMailCommand(object):
    def execute():
        #メール送信処理・・・

cmd = BeepCommand()
btn = SlickButton(cmd)

cmd2 = SendMailCommand()
btn2 = SlickButton(cmd)

音を鳴らしたりメールを出したりといった処理は、コマンドオブジェクトに任せ、
ボタンはボタン自体の処理に集中します。

アンドゥ・リドゥをするコマンド

Commandパターンを使うと、アンドゥ・リドゥを簡単に実装できます。
たとえばインストーラの場合、

  • ディレクトリ新規作成
  • ファイル新規作成
  • ファイルコピー
  • ファイル移動
  • ファイル削除
などのコマンドが必要ですが、

ファイルを作った直後に、そのファイルを削除するのは簡単です。

ファイルを移動した直後に、元の場所に戻すのも簡単です。



コマンドは、自分が実行された後、それを帳消しにする方法を知っているのです。



import os

class Command(object):
    def __init__(self, _description):
        self._description = _description
    
    def description(self):
        return self._description
    
    def execute(self):pass
    def unexecute(self):pass

class CreateCommand(Command):
    def __init__(self, path, contents):
        Command.__init__(self, "Create file: %s"%(path,))
        self.path = path
        self.contents = contents
    
    def execute(self):
        f = open(self.path, "w")
        f.write(self.contents)
        f.close()
    
    def unexecute(self):
        os.remove(self.path)

class DeleteCommand(Command):
    def __init__(self, path):
        Command.__init__(self, "Delete file: %s"%(path,))
        self.path = path
        self.contents = None
    
    def execute(self):
        try:
            f = open(self.path, "r")
        except IOError:
            pass
        else:
            self.contents = f.read()
            f.close()
        os.remove(self.path)
        
    def unexecute(self):
        if self.contents is not None:
            f = open(self.path, "w")
            f.write(self.contents)
            f.close()

class CompositeCommand(Command):
    def __init__(self):
        Command.__init__(self, None)
        self.commands = []
    
    def description(self):
        return "\n".join(cmd.description() for cmd in self.commands)
    
    def append_command(self, cmd):
        self.commands.append(cmd)
    
    def execute(self):
        for cmd in self.commands:
            cmd.execute()
        
    def unexecute(self):
        for cmd in reversed(self.commands):
            cmd.unexecute()

CreateCommandは、execute/unexecuteでファイルを作成/削除、

DeleteCommandは、execute/unexecuteでファイルを削除/復元します。

Compositeパターンを使ったCompositeCommandは、append_commandでコマンドを溜め込み、
一気に実行・復元します。