Skip to content

Commit 697deba

Browse files
authored
Merge pull request faif#322 from h4ppysmile/patch-1
changing description and example to add more clarification
2 parents 393d783 + 6ff56ea commit 697deba

File tree

1 file changed

+82
-42
lines changed

1 file changed

+82
-42
lines changed

patterns/behavioral/command.py

Lines changed: 82 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,101 @@
11
"""
2+
Command pattern decouples the object invoking a job from the one who knows
3+
how to do it. As mentioned in the GoF book, a good example is in menu items.
4+
You have a menu that has lots of items. Each item is responsible for doing a
5+
special thing and you want your menu item just call the execute method when
6+
it is pressed. To achieve this you implement a command object with the execute
7+
method for each menu item and pass to it.
8+
9+
*About the example
10+
We have a menu containing two items. Each item accepts a file name, one hides the file
11+
and the other deletes it. Both items have an undo option.
12+
Each item is a MenuItem class that accepts the corresponding command as input and executes
13+
it's execute method when it is pressed.
14+
215
*TL;DR
3-
Encapsulates all information needed to perform an action or trigger an event.
16+
Object oriented implementation of callback functions.
417
518
*Examples in Python ecosystem:
6-
Django HttpRequest (without `execute` method):
7-
https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects
19+
Django HttpRequest (without execute method):
20+
https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects
821
"""
922

10-
import os
1123

24+
class HideFileCommand:
25+
"""
26+
A command to hide a file given its name
27+
"""
1228

13-
class MoveFileCommand:
14-
def __init__(self, src, dest):
15-
self.src = src
16-
self.dest = dest
29+
def __init__(self):
30+
# an array of files hidden, to undo them as needed
31+
self._hidden_files = []
1732

18-
def execute(self):
19-
self.rename(self.src, self.dest)
33+
def execute(self, filename):
34+
print(f'hiding {filename}')
35+
self._hidden_files.append(filename)
2036

2137
def undo(self):
22-
self.rename(self.dest, self.src)
38+
filename = self._hidden_files.pop()
39+
print(f'un-hiding {filename}')
40+
41+
42+
class DeleteFileCommand:
43+
"""
44+
A command to delete a file given its name
45+
"""
46+
47+
def __init__(self):
48+
# an array of deleted files, to undo them as needed
49+
self._deleted_files = []
50+
51+
def execute(self, filename):
52+
print(f'deleting {filename}')
53+
self._deleted_files.append(filename)
2354

24-
def rename(self, src, dest):
25-
print("renaming {} to {}".format(src, dest))
26-
os.rename(src, dest)
55+
def undo(self):
56+
filename = self._deleted_files.pop()
57+
print(f'restoring {filename}')
58+
59+
60+
class MenuItem:
61+
"""
62+
The invoker class. Here it is items in a menu.
63+
"""
64+
65+
def __init__(self, command):
66+
self._command = command
67+
68+
def on_do_press(self, filename):
69+
self._command.execute(filename)
70+
71+
def on_undo_press(self):
72+
self._command.undo()
2773

2874

2975
def main():
3076
"""
31-
>>> from os.path import lexists
32-
33-
>>> command_stack = [
34-
... MoveFileCommand('foo.txt', 'bar.txt'),
35-
... MoveFileCommand('bar.txt', 'baz.txt')
36-
... ]
37-
38-
# Verify that none of the target files exist
39-
>>> assert not lexists("foo.txt")
40-
>>> assert not lexists("bar.txt")
41-
>>> assert not lexists("baz.txt")
42-
43-
# Create empty file
44-
>>> open("foo.txt", "w").close()
45-
46-
# Commands can be executed later on
47-
>>> for cmd in command_stack:
48-
... cmd.execute()
49-
renaming foo.txt to bar.txt
50-
renaming bar.txt to baz.txt
51-
52-
# And can also be undone at will
53-
>>> for cmd in reversed(command_stack):
54-
... cmd.undo()
55-
renaming baz.txt to bar.txt
56-
renaming bar.txt to foo.txt
57-
58-
>>> os.unlink("foo.txt")
77+
>>> item1 = MenuItem(DeleteFileCommand())
78+
79+
>>> item2 = MenuItem(HideFileCommand())
80+
81+
# create a file named `test-file` to work with
82+
>>> test_file_name = 'test-file'
83+
84+
# deleting `test-file`
85+
>>> item1.on_do_press(test_file_name)
86+
deleting test-file
87+
88+
# restoring `test-file`
89+
>>> item1.on_undo_press()
90+
restoring test-file
91+
92+
# hiding `test-file`
93+
>>> item2.on_do_press(test_file_name)
94+
hiding test-file
95+
96+
# un-hiding `test-file`
97+
>>> item2.on_undo_press()
98+
un-hiding test-file
5999
"""
60100

61101

0 commit comments

Comments
 (0)