在Python中使用命令模式实现撤销/重做

1 投票
2 回答
5327 浏览
提问于 2025-04-17 02:48

我看到使用命令模式是实现撤销/重做功能的一个很流行的方法。实际上,我发现可以把一系列操作堆叠起来,然后按顺序反转它们,以达到某个特定的状态。不过,我不太确定在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

显然,这个测试并没有允许堆叠多个操作来撤销。也许我完全搞错了,所以希望能得到一些帮助。

2 个回答

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)

一些想法:

  1. 想要撤销一个操作可能会很困难。比如说,你怎么撤销一条线呢?你需要找回那条线下面原本的内容。通常更简单的方法是回到一个干净的状态,然后重新执行所有的命令。
  2. 每个命令应该放在一个单独的对象里。这个对象应该保存执行这个命令所需的所有数据。
  3. 在Python中,你不需要定义一个命令类。我这样做是为了说明我希望命令对象实现哪些方法。
  4. 当你撤销操作时,重新应用所有命令可能会导致速度问题。优化这个过程留给读者自己去思考。
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)

撰写回答