コマンドパターン
CoddyのPythonジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 49/64。
Command Patternはリクエストをオブジェクトとしてカプセル化し、操作のキューイング、リクエストのログ記録、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()各コマンドは、receiver オブジェクトに対する特定の操作をカプセル化します。
実際の作業を実行する receiver を作成します:
class Light:
def turn_on(self):
print("Light is on")
def turn_off(self):
print("Light is off")コマンドを実行する invoker を作成します:
class RemoteControl:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def press_button(self):
self.command.execute()command パターンを使用します。
# レシーバーを作成
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 Pattern はリクエストを、保存したり受け渡したり、後で実行したりできるオブジェクトに変換します。invoker は操作の実行方法を知る必要はなく、command オブジェクトに対して execute() を呼び出すだけです。これにより、undo/redo、操作のキューイング、commands のログ記録といった機能を実現できます。
チャレンジ
簡単このチャレンジでは、Command Patternの必須の構成要素である基盤となるCommand基底クラスをcommand.pyに実装します。この演習では、特にカプセル化に焦点を当てます。つまり、データを非公開で保存し、propertyを通じて安全に公開します。
TODOコメントに従って、command.pyのみを変更してください。TODOコメントでは、次の実装方法が示されています。
- コマンド名をprivate attribute(
_name)として保存する - read-only property(
name)を介して公開する - コマンド名を出力する
display_info()メソッドを実装する
注: このチャレンジでは基盤となるCommandクラスのみを扱います。Invoker、Receiver、そしてundo機能については、後のレッスンで導入します。このステップを完了すると、完全なパターンが基盤とするカプセル化の土台が得られます。
あなたの実装はdriver.pyによってテストされ、次の項目が検証されます。
- 基本的な機能と出力形式
- エッジケース(空の入力、特殊文字、長い名前)
- read-only propertyの保護(
obj.name = ...を試みるとAttributeErrorが発生すること) - private
_nameattributeの存在
自分で試してみよう
# 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)}")このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Pythonオンラインコンパイラ