Command¶
The purpose of the Command
behavioral pattern is to separate objects that send requests from objects that accept their results. It does this by entering an object representing the command that executes such a process. Optionally, the command may also be able to undo the result of such a process.
By wrapping the process execution into a separate object, we keep the SOLID rules, removing the potential strong link between the sender and the recipient of the request result.
Design and example¶
The main element we need to create is a common object representing the process execution. Optionally, it may be possible to undo the execution of the command.
The objects needed to execute the command most often come in the implementation constructor.
The example below uses the Command
interface, along with two implementations: ChangeFileNameCommand
i RemoveEmptyLinesCommand
. It is possible to undo the invocation of the ChangeFileNameCommand
command with the cancel
method.
class Command:
def apply(self):
pass
def cancel(self):
pass
class PythonFile:
def __init__(self, file_name, lines_content):
self.file_name = file_name
self.lines_content = lines_content
def add_line(self, line):
self.lines_content.append(line)
def __str__(self):
return f"file_name={self.file_name}\n{self.lines_content}"
class ChangeFileNameCommand(Command):
def __init__(self, python_file, new_name):
self._python_file = python_file
self._new_name = new_name
self._previous_name = None
def apply(self):
self._previous_name = self._python_file.file_name
self._python_file.file_name = self._new_name
print(f"File name changed to: {self._new_name}")
def cancel(self):
if self._previous_name:
self._python_file.file_name = self._previous_name
self._previous_name = None
class RemoveEmptyLinesCommand(Command):
def __init__(self, python_file):
self._python_file = python_file
def apply(self):
self._python_file.lines_content = [line for line in self._python_file.lines_content if line.strip()]
def cancel(self):
print('Operation not supported!')
The client that uses the result of executing the commands:
def main():
python_file = PythonFile('test.py', ["import this", " ", "print('That's all folks!)", ""])
change_file_name_command = ChangeFileNameCommand(python_file, 'zen.py')
remove_empty_lines_command = RemoveEmptyLinesCommand(python_file)
change_file_name_command.apply()
remove_empty_lines_command.apply()
print(python_file)
change_file_name_command.cancel()
remove_empty_lines_command.cancel()
print(python_file)
if __name__ == '__main__':
main()
The result of the program will be:
File name changed to: zen.py
file_name=zen.py
['import this', "print('That's all folks!)"]
Operation not supported!
file_name=test.py
['import this', "print('That's all folks!)"]