-
Notifications
You must be signed in to change notification settings - Fork 7k
changing description and example to add more clarification #322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
119ad75
fed1e21
382cec1
969a067
ef4e73c
43bbf78
f09e3a7
ef7684b
6ff56ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,101 @@ | ||
""" | ||
Command pattern decouples the object invoking a job from the one who knows | ||
how to do it. As mentioned in the GoF book, a good example is in menu items. | ||
You have a menu that has lots of items. Each item is responsible for doing a | ||
special thing and you want your menu item just call the execute method when | ||
it is pressed. To achieve this you implement a command object with the execute | ||
method for each menu item and pass to it. | ||
|
||
*About the example | ||
We have a menu containing two items. Each item accepts a file name, one hides the file | ||
and the other deletes it. Both items have an undo option. | ||
Each item is a MenuItem class that accepts the corresponding command as input and executes | ||
it's execute method when it is pressed. | ||
|
||
*TL;DR | ||
Encapsulates all information needed to perform an action or trigger an event. | ||
Object oriented implementation of callback functions. | ||
|
||
*Examples in Python ecosystem: | ||
Django HttpRequest (without `execute` method): | ||
https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects | ||
Django HttpRequest (without execute method): | ||
https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects | ||
""" | ||
|
||
import os | ||
|
||
class HideFileCommand: | ||
""" | ||
A command to hide a file given its name | ||
""" | ||
|
||
class MoveFileCommand: | ||
def __init__(self, src, dest): | ||
self.src = src | ||
self.dest = dest | ||
def __init__(self): | ||
# an array of files hidden, to undo them as needed | ||
self._hidden_files = [] | ||
|
||
def execute(self): | ||
self.rename(self.src, self.dest) | ||
def execute(self, filename): | ||
print(f'hiding {filename}') | ||
self._hidden_files.append(filename) | ||
|
||
def undo(self): | ||
self.rename(self.dest, self.src) | ||
filename = self._hidden_files.pop() | ||
print(f'un-hiding {filename}') | ||
|
||
|
||
class DeleteFileCommand: | ||
""" | ||
A command to delete a file given its name | ||
""" | ||
|
||
def __init__(self): | ||
# an array of deleted files, to undo them as needed | ||
self._deleted_files = [] | ||
|
||
def execute(self, filename): | ||
print(f'deleting {filename}') | ||
self._deleted_files.append(filename) | ||
|
||
def rename(self, src, dest): | ||
print("renaming {} to {}".format(src, dest)) | ||
os.rename(src, dest) | ||
def undo(self): | ||
filename = self._deleted_files.pop() | ||
print(f'restoring {filename}') | ||
|
||
|
||
class MenuItem: | ||
""" | ||
The invoker class. Here it is items in a menu. | ||
""" | ||
|
||
def __init__(self, command): | ||
self._command = command | ||
|
||
def on_do_press(self, filename): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think names like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, but the invoker (here is the menu item) does not know what is going to do. Am I right? |
||
self._command.execute(filename) | ||
|
||
def on_undo_press(self): | ||
self._command.undo() | ||
|
||
|
||
def main(): | ||
""" | ||
>>> from os.path import lexists | ||
|
||
>>> command_stack = [ | ||
... MoveFileCommand('foo.txt', 'bar.txt'), | ||
... MoveFileCommand('bar.txt', 'baz.txt') | ||
... ] | ||
|
||
# Verify that none of the target files exist | ||
>>> assert not lexists("foo.txt") | ||
>>> assert not lexists("bar.txt") | ||
>>> assert not lexists("baz.txt") | ||
|
||
# Create empty file | ||
>>> open("foo.txt", "w").close() | ||
|
||
# Commands can be executed later on | ||
>>> for cmd in command_stack: | ||
... cmd.execute() | ||
renaming foo.txt to bar.txt | ||
renaming bar.txt to baz.txt | ||
|
||
# And can also be undone at will | ||
>>> for cmd in reversed(command_stack): | ||
... cmd.undo() | ||
hojatm-huma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
renaming baz.txt to bar.txt | ||
renaming bar.txt to foo.txt | ||
|
||
>>> os.unlink("foo.txt") | ||
>>> item1 = MenuItem(DeleteFileCommand()) | ||
|
||
>>> item2 = MenuItem(HideFileCommand()) | ||
|
||
# create a file named `test-file` to work with | ||
>>> test_file_name = 'test-file' | ||
|
||
# deleting `test-file` | ||
>>> item1.on_do_press(test_file_name) | ||
deleting test-file | ||
|
||
# restoring `test-file` | ||
>>> item1.on_undo_press() | ||
restoring test-file | ||
|
||
# hiding `test-file` | ||
>>> item2.on_do_press(test_file_name) | ||
hiding test-file | ||
|
||
# un-hiding `test-file` | ||
>>> item2.on_undo_press() | ||
un-hiding test-file | ||
""" | ||
|
||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.