Menu
Coddy logo textTech

コマンドパターン

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 49/64。

コマンドパターンは、リクエストをオブジェクトとしてカプセル化することで、操作のキューイング、リクエストのログ記録、および取り消し(Undo)機能のサポートを可能にします。これは、操作を呼び出すオブジェクトと、実際に操作を実行するオブジェクトを分離します。

以下はシンプルなコマンドクラスです:

class Command:
    def execute(self):
        pass

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 Light:
    def turn_on(self):
        print("Light is on")
    
    def turn_off(self):
        print("Light is 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()

# コマンドを作成
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)

# インボーカーを作成
remote = RemoteControl()

# さまざまなコマンドを実行
remote.set_command(light_on)
remote.press_button()

remote.set_command(light_off)
remote.press_button()

元に戻す(undo)操作のサポートを追加します:

class UndoableCommand(Command):
    def undo(self):
        pass

class LightOnCommand(UndoableCommand):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()
    
    def undo(self):
        self.light.turn_off()

class SmartRemote:
    def __init__(self):
        self.last_command = None
    
    def execute_command(self, command):
        command.execute()
        self.last_command = command
    
    def undo(self):
        if self.last_command:
            self.last_command.undo()

smart_remote = SmartRemote()
smart_remote.execute_command(LightOnCommand(light))
smart_remote.undo()  # ライトをオフにします

出力:

Light is on
Light is off
Light is on
Light is off

重要なポイント:Commandパターンは、リクエストを、保存、受け渡し、および後で実行可能なオブジェクトへと変換します。インボーカーは操作の実行方法を知る必要はなく、単にコマンドオブジェクトの execute() を呼び出すだけです。これにより、元に戻す/やり直し(undo/redo)、操作のキューイング、コマンドのログ記録などの機能が実現可能になります。

challenge icon

チャレンジ

簡単

このチャレンジでは、command.py基礎となる Command ベースクラス を実装します。これはコマンドパターンの不可欠な構成要素です。この演習では、特に カプセル化 に焦点を当てます。つまり、データをプライベートに保存し、プロパティを通じて安全に公開することです。

TODO コメントに従って command.py のみを修正してください。TODO コメントは以下の手順をガイドします:

  • コマンド名を プライベート属性 (_name) として保存する
  • それを 読み取り専用プロパティ (name) を介して公開する
  • コマンド名を出力する display_info() メソッドを実装する

注意: このチャレンジではベースとなる Command クラスのみを扱います。Invoker(実行者)、Receiver(受信者)、および undo(取り消し)機能は、後のレッスンで導入されます。このステップを完了することで、パターン全体が構築されるカプセル化の基礎を習得できます。

実装は driver.py によってテストされ、以下が検証されます:

  • 基本的な機能と出力形式
  • エッジケース(空の入力、特殊文字、長い名前)
  • 読み取り専用プロパティの保護(obj.name = ... の試行が AttributeError を発生させること)
  • プライベート属性 _name の存在

チートシート

Commandパターンは、リクエストをオブジェクトとしてカプセル化し、操作のキューイング、リクエストのログ記録、および取り消し(undo)機能のサポートを可能にします。

基本的なコマンド構造:

class Command:
    def execute(self):
        pass

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 Light:
    def turn_on(self):
        print("Light is on")
    
    def turn_off(self):
        print("Light is 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()

# コマンドの作成
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)

# インボーカーの作成
remote = RemoteControl()

# さまざまなコマンドの実行
remote.set_command(light_on)
remote.press_button()

remote.set_command(light_off)
remote.press_button()

取り消し(undo)機能を追加する:

class UndoableCommand(Command):
    def undo(self):
        pass

class LightOnCommand(UndoableCommand):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()
    
    def undo(self):
        self.light.turn_off()

class SmartRemote:
    def __init__(self):
        self.last_command = None
    
    def execute_command(self, command):
        command.execute()
        self.last_command = command
    
    def undo(self):
        if self.last_command:
            self.last_command.undo()

自分で試してみよう

# command.pyからCommandクラスをインポートします
from command import Command

# 包括的なテストケースハンドラー
test_case = input()

if test_case == "basic_test":
    obj = Command("Test Name")
    obj.display_info()
elif test_case == "validation_test":
    obj = Command("Validation Test")
    print(f"Name: {obj.name}")
elif test_case == "empty_name_test":
    obj = Command("")
    obj.display_info()
    print(f"Empty name handled: {'Yes' if obj.name == '' else 'No'}")
elif test_case == "property_access_test":
    obj = Command("Property Test")
    original_name = obj.name
    try:
        # nameは読み取り専用プロパティであるため、これは失敗するはずです
        obj.name = "Modified Name"
        print("Property protection failed")
    except AttributeError:
        print("Property protected successfully")
    print(f"Name unchanged: {obj.name == original_name}")
elif test_case == "multiple_commands_test":
    commands = [
        Command("First Command"),
        Command("Second Command"),
        Command("Third Command")
    ]
    for cmd in commands:
        cmd.display_info()
elif test_case == "attribute_test":
    obj = Command("Attribute Test")
    has_private_name = hasattr(obj, "_name")
    print(f"Has _name attribute: {has_private_name}")
elif test_case == "special_chars_test":
    obj = Command("!@#$%^&*()_+{}[]|\\:;\"'<>,.?/")
    obj.display_info()
elif test_case == "long_name_test":
    long_name = "A" * 100
    obj = Command(long_name)
    obj.display_info()
    print(f"Name length: {len(obj.name)}")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン