Command

コマンドパターン(Command Pattern)は、オブジェクト指向のデザインパターンの一つで、アクションをオブジェクトとしてカプセル化することで、アクションの実行者とアクションのリクエスト者を分離します。このパターンは特に、操作が行われるタイミングを制御したいときや、履歴管理、トランザクションの管理などが必要な場合に有効です。

コマンドパターンの構成要素:

  1. Command: 実行するアクションに対応するインターフェース。

  2. ConcreteCommand: Commandインターフェースを実装し、特定のReceiverとアクションを関連付ける。

  3. Client: Commandオブジェクトを生成し、Invokerに渡す。

  4. Invoker: Commandを保持し、特定のタイミングでCommandのメソッドを実行する。

  5. Receiver: 実際のアクションの実行者。

Pythonでのコマンドパターンの例:

from abc import ABC, abstractmethod

class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

class Light:
    def turn_on(self):
        print("Light is on.")

    def turn_off(self):
        print("Light is off.")

class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_on()

class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_off()

class RemoteControl:
    def __init__(self):
        self.command = None

    def set_command(self, command):
        self.command = command

    def press_button(self):
        self.command.execute()

# 使用例
light = Light()
turn_on_command = LightOnCommand(light)
turn_off_command = LightOffCommand(light)

remote = RemoteControl()
remote.set_command(turn_on_command)
remote.press_button()
remote.set_command(turn_off_command)
remote.press_button()

この例では、Lightオブジェクトのオン/オフの操作をLightOnCommandLightOffCommandクラスでコマンドとしてカプセル化しています。RemoteControlクラスはInvokerであり、設定されたコマンドを実行します。このようにコマンドパターンを使用すると、アクションの実行ロジックを呼び出し元から分離し、拡張性と再利用性を向上させることができます。

直接Lightクラスのturn_onturn_offメソッドを使用する方が直感的でわかりやすい場合もあります。ただし、コマンドパターンの目的は、実行すべきアクション(この場合はライトのオン/オフ)をオブジェクトとしてカプセル化し、その実行を管理することにより、より複雑な制御や柔軟性、後からの変更の容易さを提供することにあります。たとえば、アクションの履歴を保持して「元に戻す」「やり直し」の機能を実装する場合や、複数の異なる操作を組み合わせてマクロコマンドを作成する場合に役立ちます。

コマンドパターンの利点を示すために、元の例を少し拡張してみましょう。以下のコードでは、リモートコントロールが複数のコマンドを管理し、これらを実行する機能を持つようにします。これにより、単純なオン/オフだけでなく、複雑な操作を一度に行うことができるようになります。

拡張されたコマンドパターンの例:

from abc import ABC, abstractmethod

class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

class Light:
    def turn_on(self):
        print("Light is on.")

    def turn_off(self):
        print("Light is off.")

class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_on()

class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_off()

class MacroCommand(Command):
    def __init__(self, commands):
        self.commands = commands

    def execute(self):
        for command in self.commands:
            command.execute()

class RemoteControl:
    def __init__(self):
        self.commands = []

    def add_command(self, command):
        self.commands.append(command)

    def press_buttons(self):
        for command in self.commands:
            command.execute()

# 使用例
light = Light()
turn_on_command = LightOnCommand(light)
turn_off_command = LightOffCommand(light)

macro_command = MacroCommand([turn_on_command, turn_off_command])

remote = RemoteControl()
remote.add_command(turn_on_command)
remote.add_command(turn_off_command)
remote.add_command(macro_command)
remote.press_buttons()  # シーケンスに従って各コマンドを実行

このように、コマンドパターンを使用すると、単一のインターフェイスを通じて複数のコマンドを柔軟に扱うことができます。特に複数のデバイスや機能を同時に制御する必要があるシステムにおいて、コマンドパターンはその真価を発揮します。

もう少し実践的なシナリオでコマンドパターンの有用性を示してみましょう。よくある使用例として、複数の異なる種類のデバイスを同じインターフェースで操作することが挙げられます。ここでは、ライトとファン(扇風機)をコントロールする例を見てみましょう。

このケースでは、異なるデバイスに対して共通のインターフェース(コマンド)を使用して操作を行います。コマンドを利用することで、拡張性が高まり、新しいデバイスの追加や異なる種類のコマンドの組み合わせが容易になります。

実践的なコマンドパターンの例:

from abc import ABC, abstractmethod

class Command(ABC):
    """
    抽象クラス:コマンドを表す

    メソッド:
        execute(): コマンドを実行する
        undo(): コマンドを元に戻す
    """

    @abstractmethod
    def execute(self):
        pass

    @abstractmethod
    def undo(self):
        pass


class TextEditor:
    """
    テキストエディタを表すクラス
    """

    def __init__(self):
        self.content = ""  # テキスト内容

    def write(self, text):
        """
        テキストを追加する
        """
        self.content += text

    def delete_last(self, length):
        """
        最後のlength文字を削除する
        """
        self.content = self.content[:-length]

    def read(self):
        """
        現在のテキスト内容を取得する
        """
        return self.content


class WriteCommand(Command):
    """
    "書き込み" コマンドを表すクラス
    """

    def __init__(self, editor, text):
        self.editor = editor  # テキストエディタ
        self.text = text  # 書き込むテキスト

    def execute(self):
        """
        テキストを書き込む
        """
        self.editor.write(self.text)

    def undo(self):
        """
        書き込みを元に戻す
        """
        self.editor.delete_last(len(self.text))


class EditorHistory:
    """
    エディタ操作履歴を管理するクラス
    """

    def __init__(self):
        self.commands = []  # 実行したコマンドのリスト

    def execute_command(self, command):
        """
        コマンドを実行し、履歴に追加する
        """
        command.execute()
        self.commands.append(command)

    def undo(self):
        """
        最後の操作を元に戻す
        """
        if self.commands:
            command = self.commands.pop()
            command.undo()


# 使用例
editor = TextEditor()
history = EditorHistory()

# "Hello" を書き込むコマンドを実行
history.execute_command(WriteCommand(editor, "Hello"))

# "World" を書き込むコマンドを実行
history.execute_command(WriteCommand(editor, " World"))

# テキスト内容を出力
print(editor.read())  # 出力: Hello World

# 最後の操作を元に戻す
history.undo()

# テキスト内容を出力
print(editor.read())  # 出力: Hello

# もう一度元に戻す
history.undo()

# テキスト内容を出力
print(editor.read())  # 出力: (空文字列)

この例では、リモートコントロールが各ボタンに対して異なるコマンドを設定しています。ライトとファンの操作を個別のコマンドオブジェクトとしてカプセル化することにより、異なるデバイスの操作を同一の方法で扱えるようになっています。これにより、異なる種類のデバイスが増えた場合でも、新しいコマンドクラスを追加するだけで対応可能です。

まとめ

コマンドパターンは、異なるクラスのメソッドを一つのインターフェースで統一的に扱うことを可能にします。このパターンを使うことで、特定の操作を「コマンド」としてカプセル化し、これらのコマンドを実行する「インボーカー」(例ではRemoteControl)がそれを管理します。これにより、以下のような利点があります:

  1. 拡張性の向上

    • 新しいコマンドを追加するのが簡単で、既存のコードを変更することなく新しい機能を組み込むことができます。例えば、新しいデバイスや新しい操作が必要になった場合、新しいCommandサブクラスを作成するだけで対応可能です。

  2. 再利用性と分離の促進

    • コマンドオブジェクトを通じて操作をカプセル化することで、特定のアクションやリクエストを多くの異なるコンテキストで再利用できるようになります。また、コマンドの発行者と実行者が分離されるため、それぞれ独立して開発や保守が行えるようになります。

  3. 動的なコマンド管理

    • 実行時にどのコマンドを使うかを動的に決定できるため、より柔軟な設計が可能になります。例えば、ユーザーの選択や設定に基づいて異なるコマンドを実行することが容易になります。

コマンドパターンは、特にユーザーインタフェースが複雑なアプリケーションや、多くの異なる操作やデバイスを一元管理したい場合に有効です。また、履歴機能(Undo/Redo)を実装する際にもこのパターンは非常に役立ちます。このように、コマンドパターンはプログラムの柔軟性とメンテナンスの容易さを大きく向上させることができる強力なデザインパターンです。

この記事が気に入ったらサポートをしてみませんか?