From 119ad75c571a4bd033088ee11b1dcecf80825e73 Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Thu, 12 Mar 2020 10:57:52 +0330 Subject: [PATCH 1/9] Update commad pattern add more clarification --- patterns/behavioral/command.py | 92 +++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index e2b88394..87ab2ff5 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -1,61 +1,71 @@ """ +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. + *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 +https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects """ -import os +class Command: + """ + The interface that commands implement. This is used to abstract invoker from + the command is going to handle the job. + """ + + def execute(self): + raise NotImplementedError() -class MoveFileCommand: - def __init__(self, src, dest): - self.src = src - self.dest = dest + +class MakeBoldCommand(Command): + """ + A simple command to bold a text. + """ def execute(self): - self.rename(self.src, self.dest) + print('I am making it bold.') - def undo(self): - self.rename(self.dest, self.src) - def rename(self, src, dest): - print("renaming {} to {}".format(src, dest)) - os.rename(src, dest) +class MakeItalicCommand(Command): + """ + A simple command to italic a text. + """ + + def execute(self): + print('I am making it italic.') + + +class MenuItem: + """ + The invoker class. Here it is items in a menu. + """ + + def __init__(self, command): + self._command = command + + def on_press(self): + self._command.execute() 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() - renaming baz.txt to bar.txt - renaming bar.txt to foo.txt - - >>> os.unlink("foo.txt") + >>> item1 = MenuItem(MakeBoldCommand()) + + >>> item2 = MenuItem(MakeItalicCommand()) + + >>> item1.on_press() + I am making it bold. + + >>> item2.on_press() + I am making it italic. """ From fed1e212567c693ce6d8036b39c798c6ed0204fb Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Sat, 14 Mar 2020 11:30:18 +0330 Subject: [PATCH 2/9] changed the example to implement undo feature --- patterns/behavioral/command.py | 102 +++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index 87ab2ff5..13c7ffea 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -6,6 +6,12 @@ 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 accept a file name, one hides the file +and the other deletes it. Both items have undo option. +Each item is a MenuItem class that accept corresponding command as input and executes +its `execute` method when it is pressed. + *TL;DR Object oriented implementation of callback functions. @@ -14,33 +20,65 @@ https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects """ +import os + -class Command: +class HideFileCommand: """ - The interface that commands implement. This is used to abstract invoker from - the command is going to handle the job. + A command to hide a file given its name """ - def execute(self): - raise NotImplementedError() + def __init__(self): + # an array of files hidden, to undo them as needed + self._hidden_files = [] + def execute(self, filename): + if os.path.isfile(filename): + print(f'hiding {filename}') -class MakeBoldCommand(Command): - """ - A simple command to bold a text. - """ + os.rename(filename, f'.{filename}') + self._hidden_files.append(filename) + else: + print(f'{filename} dose not exists to hide') + + def undo(self): + if len(self._hidden_files) > 0: + filename = self._hidden_files.pop() - def execute(self): - print('I am making it bold.') + print(f'un-hiding {filename}') + os.rename(f'.{filename}', filename) -class MakeItalicCommand(Command): + +class DeleteFileCommand: """ - A simple command to italic a text. + A command to delete a file given its name """ - def execute(self): - print('I am making it italic.') + def __init__(self): + # an array of deleted files, to undo them as needed + self._deleted_files = [] + + # create a directory to store deleted files + if not os.path.exists('bin'): + os.makedirs('bin') + + def execute(self, filename): + if os.path.isfile(filename): + print(f'deleting {filename}') + + os.rename(filename, f'bin/{filename}') + self._deleted_files.append(filename) + else: + print(f'{filename} dose not exists to delete') + + def undo(self): + if len(self._deleted_files) > 0: + filename = self._deleted_files.pop() + + print(f'un-deleting {filename}') + + os.rename(f'bin/{filename}', filename) class MenuItem: @@ -51,24 +89,42 @@ class MenuItem: def __init__(self, command): self._command = command - def on_press(self): - self._command.execute() + def on_do_press(self, filename): + self._command.execute(filename) + + def on_undo_press(self): + self._command.undo() def main(): """ - >>> item1 = MenuItem(MakeBoldCommand()) + >>> item1 = MenuItem(DeleteFileCommand()) - >>> item2 = MenuItem(MakeItalicCommand()) + >>> item2 = MenuItem(HideFileCommand()) - >>> item1.on_press() - I am making it bold. + # create a file named `test-file` to work with + >>> test_file_name = 'test-file' + >>> open(test_file_name, 'w').close() - >>> item2.on_press() - I am making it italic. + # deleting `test-file` + >>> item1.on_do_press(test_file_name) + deleting test-file + + # hiding `test-file` but it dose not exists + >>> item2.on_do_press(test_file_name) + test-file dose not exists to hide + + # un-deleting `test-file` + >>> item1.on_undo_press() + un-deleting test-file + + # hiding `test-file` + >>> item2.on_do_press(test_file_name) + hiding test-file """ if __name__ == "__main__": import doctest + doctest.testmod() From 382cec1e986c04266c2d96d31358cf83c1c97e1a Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Sat, 14 Mar 2020 11:32:52 +0330 Subject: [PATCH 3/9] a little type error --- patterns/behavioral/command.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index 13c7ffea..d8426492 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -7,10 +7,10 @@ method for each menu item and pass to it. *About the example -We have a menu containing two items. Each item accept a file name, one hides the file -and the other deletes it. Both items have undo option. -Each item is a MenuItem class that accept corresponding command as input and executes -its `execute` method when it is pressed. +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 Object oriented implementation of callback functions. From 969a0673a887013daa63dd8051b1a7bfc7955cf3 Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Sun, 15 Mar 2020 09:13:25 +0330 Subject: [PATCH 4/9] changing deleted files storage path --- patterns/behavioral/command.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index d8426492..a1c079b6 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -10,7 +10,7 @@ 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. +it's `execute` method when it is pressed. *TL;DR Object oriented implementation of callback functions. @@ -54,20 +54,21 @@ class DeleteFileCommand: """ A command to delete a file given its name """ + _deleted_files_path = 'trash' def __init__(self): # an array of deleted files, to undo them as needed self._deleted_files = [] # create a directory to store deleted files - if not os.path.exists('bin'): - os.makedirs('bin') + if not os.path.exists(self._deleted_files_path): + os.makedirs(self._deleted_files_path) def execute(self, filename): if os.path.isfile(filename): print(f'deleting {filename}') - os.rename(filename, f'bin/{filename}') + os.rename(filename, f'{self._deleted_files_path}/{filename}') self._deleted_files.append(filename) else: print(f'{filename} dose not exists to delete') @@ -78,7 +79,7 @@ def undo(self): print(f'un-deleting {filename}') - os.rename(f'bin/{filename}', filename) + os.rename(f'{self._deleted_files_path}/{filename}', filename) class MenuItem: From ef4e73c04f72ca3c63b7a79c2dee948cf632bfcf Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Sun, 15 Mar 2020 14:43:33 +0330 Subject: [PATCH 5/9] fixed does typo --- patterns/behavioral/command.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index a1c079b6..6f8965be 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -39,7 +39,7 @@ def execute(self, filename): os.rename(filename, f'.{filename}') self._hidden_files.append(filename) else: - print(f'{filename} dose not exists to hide') + print(f'{filename} does not exists to hide') def undo(self): if len(self._hidden_files) > 0: @@ -71,7 +71,7 @@ def execute(self, filename): os.rename(filename, f'{self._deleted_files_path}/{filename}') self._deleted_files.append(filename) else: - print(f'{filename} dose not exists to delete') + print(f'{filename} does not exists to delete') def undo(self): if len(self._deleted_files) > 0: @@ -111,9 +111,9 @@ def main(): >>> item1.on_do_press(test_file_name) deleting test-file - # hiding `test-file` but it dose not exists + # hiding `test-file` but it does not exists >>> item2.on_do_press(test_file_name) - test-file dose not exists to hide + test-file does not exists to hide # un-deleting `test-file` >>> item1.on_undo_press() From 43bbf780ce3e87f0040406c04a01fd79a8d9e16b Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Sun, 15 Mar 2020 14:45:14 +0330 Subject: [PATCH 6/9] un-delete changed to restore --- patterns/behavioral/command.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index 6f8965be..96092d11 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -77,7 +77,7 @@ def undo(self): if len(self._deleted_files) > 0: filename = self._deleted_files.pop() - print(f'un-deleting {filename}') + print(f'restoring {filename}') os.rename(f'{self._deleted_files_path}/{filename}', filename) @@ -115,9 +115,9 @@ def main(): >>> item2.on_do_press(test_file_name) test-file does not exists to hide - # un-deleting `test-file` + # restoring `test-file` >>> item1.on_undo_press() - un-deleting test-file + restoring test-file # hiding `test-file` >>> item2.on_do_press(test_file_name) From f09e3a740e1bfd73091fcaac8f5e45cbca432f58 Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Sun, 15 Mar 2020 14:50:50 +0330 Subject: [PATCH 7/9] removed really deleting or renaming files --- patterns/behavioral/command.py | 51 ++++++++-------------------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index 96092d11..72e7b8d9 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -20,8 +20,6 @@ https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects """ -import os - class HideFileCommand: """ @@ -33,53 +31,30 @@ def __init__(self): self._hidden_files = [] def execute(self, filename): - if os.path.isfile(filename): - print(f'hiding {filename}') - - os.rename(filename, f'.{filename}') - self._hidden_files.append(filename) - else: - print(f'{filename} does not exists to hide') + print(f'hiding {filename}') + self._hidden_files.append(filename) def undo(self): - if len(self._hidden_files) > 0: - filename = self._hidden_files.pop() - - print(f'un-hiding {filename}') - - os.rename(f'.{filename}', filename) + filename = self._hidden_files.pop() + print(f'un-hiding {filename}') class DeleteFileCommand: """ A command to delete a file given its name """ - _deleted_files_path = 'trash' def __init__(self): # an array of deleted files, to undo them as needed self._deleted_files = [] - # create a directory to store deleted files - if not os.path.exists(self._deleted_files_path): - os.makedirs(self._deleted_files_path) - def execute(self, filename): - if os.path.isfile(filename): - print(f'deleting {filename}') - - os.rename(filename, f'{self._deleted_files_path}/{filename}') - self._deleted_files.append(filename) - else: - print(f'{filename} does not exists to delete') + print(f'deleting {filename}') + self._deleted_files.append(filename) def undo(self): - if len(self._deleted_files) > 0: - filename = self._deleted_files.pop() - - print(f'restoring {filename}') - - os.rename(f'{self._deleted_files_path}/{filename}', filename) + filename = self._deleted_files.pop() + print(f'restoring {filename}') class MenuItem: @@ -105,16 +80,11 @@ def main(): # create a file named `test-file` to work with >>> test_file_name = 'test-file' - >>> open(test_file_name, 'w').close() # deleting `test-file` >>> item1.on_do_press(test_file_name) deleting test-file - # hiding `test-file` but it does not exists - >>> item2.on_do_press(test_file_name) - test-file does not exists to hide - # restoring `test-file` >>> item1.on_undo_press() restoring test-file @@ -122,10 +92,13 @@ def main(): # 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 """ if __name__ == "__main__": import doctest - doctest.testmod() From ef7684b89710c7d92fb25342119321347bf761f2 Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Tue, 17 Mar 2020 08:49:38 +0330 Subject: [PATCH 8/9] remove apostopher from execute --- patterns/behavioral/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index 72e7b8d9..5fde53e4 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -10,7 +10,7 @@ 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. +it's execute method when it is pressed. *TL;DR Object oriented implementation of callback functions. From 6ff56ea7303d7c3ea700bfe465c76d76d7530aa8 Mon Sep 17 00:00:00 2001 From: Hojat Modaresi Zade Date: Tue, 17 Mar 2020 08:50:23 +0330 Subject: [PATCH 9/9] remove apostrophe from execute --- patterns/behavioral/command.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patterns/behavioral/command.py b/patterns/behavioral/command.py index 5fde53e4..c989d2a9 100644 --- a/patterns/behavioral/command.py +++ b/patterns/behavioral/command.py @@ -2,8 +2,8 @@ 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` +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 @@ -16,7 +16,7 @@ Object oriented implementation of callback functions. *Examples in Python ecosystem: -Django HttpRequest (without `execute` method): +Django HttpRequest (without execute method): https://docs.djangoproject.com/en/2.1/ref/request-response/#httprequest-objects """