Do/Undo using command pattern in Python
I have read that using command pattern is one of the most popular ways to accomplish do/undo functionality. In fact, I have seen that it's possible to stack a bunch of actions and reverse them in order to reach a given state. However, I'm not quite sure how that can be done in Python and most of the tutorials I have read, dabble into concepts but don't show an actual implementation in Python.
Does anyone know how do/undo functionality work in Python?
For reference, this is my (naive and probably ridden with errors) code:
# 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))
instantiating as follows:
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()
output:
Draw a line from 1 to 2
Erase a line from 1 to 2
Obviously, this tes开发者_Go百科t doesn't allow stack several actions to undo. Maybe I'm completely mistaken so I would appreciate some help.
Here is an implementation keeping the commands in a list.
# 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)
How I'd go about this
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)
Some thoughts:
- Trying to undo an action can be hard. For example, how would you undraw a line? You'd need to recover what used to be under that line. A simpler approach is often to revert to a clean slate and then reapply all the commands.
- Each command should be contained in a single object. It should store all of the data neccesary for the command.
- In python you don't need to define the Command class. I do it to provide documentation for what methods I expect Command objects to implement.
- You may eventually get speed issues reapplying all the command for an undo. Optimization is left as an excersize for the reader.
精彩评论