在Python中使用命令模式执行/撤消

在Python中使用命令模式执行/撤消,python,design-patterns,command,Python,Design Patterns,Command,我已经读到,使用命令模式是实现do/undo功能的最流行的方法之一。事实上,我已经看到,为了达到一个给定的状态,可以堆叠一系列动作并反转它们。但是,我不太确定如何在Python中实现这一点,我读过的大多数教程都涉及到了一些概念,但没有显示Python中的实际实现 有人知道Python中的/undo功能是如何工作的吗 作为参考,这是我的(幼稚且可能充满错误)代码: 实例化如下: invoke_draw = InvokeDrawALine() draw_a_line = DrawALine() dr

我已经读到,使用命令模式是实现do/undo功能的最流行的方法之一。事实上,我已经看到,为了达到一个给定的状态,可以堆叠一系列动作并反转它们。但是,我不太确定如何在Python中实现这一点,我读过的大多数教程都涉及到了一些概念,但没有显示Python中的实际实现

有人知道Python中的/undo功能是如何工作的吗

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

实例化如下:

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
显然,此测试不允许堆栈中的多个操作撤消。也许我完全弄错了,所以我希望能得到一些帮助。

我该怎么办

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)
一些想法:

  • 试图撤销某个操作可能很困难。例如,如何取消绘制线条?你需要恢复那条线下的东西。一种更简单的方法通常是恢复原状,然后重新应用所有命令
  • 每个命令都应包含在单个对象中。它应该存储命令所需的所有数据
  • 在python中,不需要定义命令类。我这样做是为了提供我期望命令对象实现的方法的文档
  • 重新应用所有撤消命令最终可能会遇到速度问题。优化留给读者作为练习
  • 我该怎么办

    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)
    
    一些想法:

  • 试图撤销某个操作可能很困难。例如,如何取消绘制线条?你需要恢复那条线下的东西。一种更简单的方法通常是恢复原状,然后重新应用所有命令
  • 每个命令都应包含在单个对象中。它应该存储命令所需的所有数据
  • 在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)
    # 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)
    

    下面是一个将命令保存在列表中的实现

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

    非常干净漂亮。谢谢你的回答。不过我有个问题。这似乎不是命令模式的教科书式实现。是吗?。在命令子类中有接收方的作业。尽管如此,看起来我需要在列表中保留命令类的实例。@RobertSmith,与receiver类等效的是我正在传递的canvas对象。它是动作作用的目标对象。所以不,我不是在梳理receiver和command类的工作。在我看来,您的接收器实现中包含一些命令类。接收者应该对命令类/undo结构一无所知。它只知道如何执行诸如画线、画圆等方法。我理解。这是命令模式的一个版本。至于command类中的接收者,我不是说你要将它们组合起来,但是,根据你的解释,你要将接收者的等价物传递到command类中(这与我的实现中所做的完全相同)。@RobertSmith,不完全相同。。。我将其传递给execute(),您将其传递给构造函数。在我看来,你对这种模式的细节仍然有点困惑。但那不是我鼻子上的皮。您可能还对python中的这个命令模式示例感兴趣:当然,您将其传递给了execute(),但这并不完全是第269页中所述的命令模式(很抱歉,Google Books中没有该模式)。记住,我现在只想学习基础知识,所以这不是真正的实现。因此,当接收器、命令和调用程序之间有明确的区别时,我发现更容易理解它。非常干净和漂亮。谢谢你的回答。不过我有个问题。这似乎不是命令模式的教科书式实现。是吗?。在命令子类中有接收方的作业。尽管如此,看起来我需要在列表中保留命令类的实例。@RobertSmith,与receiver类等效的是我正在传递的canvas对象。它是动作作用的目标对象。所以不,我不是在梳理receiver和command类的工作。在我看来,您的接收器实现中包含一些命令类。接收者应该对命令类/undo结构一无所知。它只知道如何执行诸如画线、画圆等方法。我理解。这是命令模式的一个版本。至于command类中的接收者,我不是说你要将它们组合起来,但是,根据你的解释,你要将接收者的等价物传递到command类中(这与我的实现中所做的完全相同)。@RobertSmith,不完全相同。。。我将其传递给execute(),您将其传递给构造函数。在我看来,你对这种模式的细节仍然有点困惑。但那不是我鼻子上的皮。您可能还对python中的这个命令模式示例感兴趣:当然,您将其传递给了execute(),但这并不完全是第269页中所述的命令模式(很抱歉,Google Books中没有该模式)。记住,我现在只想学习基础知识,所以这不是真正的实现。因此,当接收者、命令和调用者之间有明确的区别时,我发现更容易理解它。谢谢你的回答。这看起来很好,但是self.commandlist不应该被分配给一个列表,而不是调用的lawlines类中的“data”吗?当然可以,但是这样你可以使用它:draw1=DrawCommand(draw\u a\u line,5,10)draw2=DrawCommand(draw\u\u line,29,55)draw3=DrawCommand(draw\u\u\u line,99,0)