【问题标题】:Do/Undo using command pattern in Python在 Python 中使用命令模式执行/撤消
【发布时间】:2011-11-22 11:22:46
【问题描述】:

我已经读到使用命令模式是完成执行/撤消功能的最流行的方法之一。事实上,我已经看到可以堆叠一堆动作并反转它们以达到给定状态。但是,我不太确定如何在 Python 中做到这一点,而且我阅读的大多数教程都涉足概念,但没有展示 Python 中的实际实现。

有谁知道 Python 中的执行/撤消功能是如何工作的?

作为参考,这是我的(幼稚且可能充满错误)代码:

# command
class DrawCommand:
    def __init__(self, draw, point1, point2):
        self.draw = draw
        self.point1 = point1
        self.point2 = point2
    def execute_drawing(self):
        self.draw.execute(self.point1, self.point2)
    def execute_undrawing(self):
        self.draw.unexecute(self.point1, self.point2)
# invoker
class InvokeDrawALine:
    def command(self, command):
        self.command = command
    def click_to_draw(self):
        self.command.execute_drawing()
    def undo(self):
        self.command.execute_undrawing()
# receiver
class DrawALine:
    def execute(self, point1, point2):
        print("Draw a line from {} to {}".format(point1, point2))
    def unexecute(self, point1, point2):
        print("Erase a line from {} to {}".format(point1, point2))

实例化如下:

invoke_draw = InvokeDrawALine()
draw_a_line = DrawALine()
draw_command = DrawCommand(draw_a_line, 1, 2)
invoke_draw.command(draw_command)
invoke_draw.click_to_draw()
invoke_draw.undo()

输出:

Draw a line from 1 to 2
Erase a line from 1 to 2

显然,此测试不允许堆叠多个操作来撤消。也许我完全错了,所以我会很感激一些帮助。

【问题讨论】:

    标签: python design-patterns command


    【解决方案1】:

    我会怎么做

    class Command(object):
        def execute(self, canvas):
             raise NotImplementedError
    
    class DrawLineCommand(Command):
        def __init__(self, point1, point2):
            self._point1 = point1
            self._point2 = point2
    
        def execute(self, canvas):
            canvas.draw_line(self._point1, self._point2)
    
     class DrawCircleCommand(Command):
         def __init__(self, point, radius):
            self._point = point
            self._radius = radius
    
         def execute(self, canvas):
            canvas.draw_circle(self._point, self._radius)
    
    class UndoHistory(object):
        def __init__(self, canvas):
            self._commands = []
            self.canvas = canvas
    
        def command(self, command):
            self._commands.append(command)
            command.execute(self.canvas)
    
        def undo(self):
            self._commands.pop() # throw away last command
            self.canvas.clear()
            for command self._commands:
                command.execute(self.canvas)
    

    一些想法:

    1. 尝试撤消操作可能很困难。例如,您将如何取消绘制一条线?您需要恢复该线下的内容。一种更简单的方法通常是恢复到全新状态,然后重新应用所有命令。
    2. 每个命令都应该包含在一个对象中。它应该存储命令所需的所有数据。
    3. 在 python 中,您不需要定义 Command 类。我这样做是为了提供有关我希望 Command 对象实现哪些方法的文档。
    4. 您最终可能会在重新应用所有命令以进行撤消时遇到速度问题。优化留给读者作为补充。

    【讨论】:

    • 非常干净漂亮。感谢您的回答。但我有个问题。这似乎不是命令模式的教科书实现。这样对吗?。您在命令子类中拥有接收器的工作。尽管如此,看起来我需要将命令类的实例保存在列表中。
    • @RobertSmith,相当于接收器类的是我正在传递的画布对象。它是动作作用于的目标对象。所以不,我不是在梳理接收器和命令类的工作。在我看来,您对接收器的实现中有一些命令类。接收者应该对命令类/撤消结构一无所知。它只知道如何执行draw_line、draw_circle等方法。
    • 我明白了。嗯,这是命令模式的一个版本。至于命令类中的接收器,我并不是说您正在组合它们,但是,根据您的说明,您将接收器的等价物传递给命令类(这与我在实现中所做的完全相同) .
    • @RobertSmith,不完全一样...我将它传递给 execute(),然后将它传递给构造函数。在我看来,您对这种模式的细节仍然有些困惑。但这不是我鼻子上的皮肤。您可能也有兴趣查看 python 中的命令模式示例:en.wikipedia.org/wiki/Command_pattern
    • 当然,您将它传递给了 execute(),但这与第 269 页 amazon.com/Python-3-Object-Oriented-Programming/dp/1849511268 中描述的命令模式不完全一致(抱歉,它在 Google 图书中不可用)。请记住,我现在只想学习基础知识,所以这不是真正的实现。因此,当接收者、命令和调用者之间有明确的区别时,我发现它更容易理解。
    【解决方案2】:

    这是一个将命令保存在列表中的实现。

    # command
    class DrawCommand:
        def __init__(self, draw, point1, point2):
            self.draw = draw
            self.point1 = point1
            self.point2 = point2
        def execute_drawing(self):
            self.draw.execute(self.point1, self.point2)
    # invoker
    class InvokeDrawLines:
        def __init__(self, data):
            self.commandlist = data
        def addcommand(self, command):
            self.commandlist.append(command)
        def draw(self):
            for cmd in self.commandlist:
                cmd.execute_drawing()
        def undocommand(self, command):
            self.commandlist.remove(command)
    
    # receiver
    class DrawALine:
        def execute(self, point1, point2):
            print("Draw a line from" , point1, point2)
    

    【讨论】:

    • 感谢您的回答。这看起来不错,但不应该将 self.commandlist 分配给列表而不是 InvokeDrawLines 类中的“数据”吗?
    • 当然,但是这样你可以像这样使用它:draw1 = DrawCommand(draw_a_line, 5, 10) draw2 = DrawCommand(draw_a_line, 29, 55) draw3 = DrawCommand(draw_a_line, 99, 0) 调用者= InvokeDrawLines([draw1,draw2,draw3])
    • 哦,很公平。然后在删除 DrawCommand 的一个实例后,我只是重新运行 InvokeDrawLines 的 draw 方法。是这样吗?
    猜你喜欢
    • 2015-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-23
    • 2020-05-05
    • 1970-01-01
    • 2011-03-16
    • 2016-11-18
    相关资源
    最近更新 更多