Menu
CoddyTech

命令模式

本课属于 Coddy Python 学习旅程的“面向对象编程”单元。第 49 课,共 64 课。

命令模式将请求封装为对象,让你能够对操作进行排队、记录请求,并支持撤销功能。它将调用操作的对象与执行操作的对象分离。

以下是一些简单的命令类:

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()

添加对撤销操作的支持:

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

要点:命令模式将请求转换为对象,这些对象可以被存储、传递并在之后执行。调用者不需要知道如何执行操作,只需在命令对象上调用 execute()。这样就能实现撤销/重做、将操作加入队列以及记录命令等功能。

challenge icon

挑战

简单

在这个挑战中,你将实现 command.py 中的基础 Command 基类,它是命令模式的核心构建块。本练习重点关注封装:私下存储数据,并通过属性安全地公开数据。

请根据 TODO 注释,只修改 command.py。TODO 注释会引导你完成以下操作:

  • 将命令名称存储为私有属性(_name)
  • 通过只读属性(name)公开它
  • 实现一个 display_info() 方法,用于打印命令名称

注意:本挑战只涉及基础 Command 类:调用者、接收者和撤销功能会在后续课程中介绍。完成这一步后,你就掌握了封装基础,完整模式将建立在此基础之上。

你的实现将由 driver.py 测试,它会检查:

  • 基本功能和输出格式
  • 边缘情况(空输入、特殊字符、较长名称)
  • 只读属性保护(尝试 obj.name = ... 应引发 AttributeError)
  • 是否存在私有 _name 属性

亲手试试

# 从 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自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Python 在线编译器