diff --git a/README.md b/README.md index 4a243da..8aa8087 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,4 @@ and divided into three groups. ### [Behavioral Patterns](behavioral/README.md) - [Chain of Responsibility](behavioral/chain_responsibility/README.md) +- [Command](behavioral/command/README.md) diff --git a/behavioral/README.md b/behavioral/README.md index c320506..7a60d66 100644 --- a/behavioral/README.md +++ b/behavioral/README.md @@ -4,4 +4,5 @@ Behavioral design patterns are concerned with algorithms and the assignment of r ## Patterns -- [Chain of Responsibility](chain_responsibility/README.md) \ No newline at end of file +- [Chain of Responsibility](chain_responsibility/README.md) +- [Command](command/README.md) diff --git a/behavioral/command/README.md b/behavioral/command/README.md new file mode 100644 index 0000000..00d678d --- /dev/null +++ b/behavioral/command/README.md @@ -0,0 +1,55 @@ +# Command + +Command is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you parameterize methods with different requests, delay or queue a request’s execution, and support undoable operations. + +## Problem + +Imagine that you’re working on a new text-editor app. Your current task is to create a toolbar with a bunch of buttons for various operations of the editor. You created a very neat `Button` class that can be used for buttons on the toolbar, as well as for generic buttons in various dialogs. + +While all of these buttons look similar, they’re all supposed to do different things. Where would you put the code for the various click handlers of these buttons? The simplest solution is to create tons of subclasses for each place where the button is used. These subclasses would contain the code that would have to be executed on a button click. + +Before long, you realize that this approach is deeply flawed. First, you have an enormous number of subclasses, and + that would be okay if you weren't risking breaking the code in these subclasses each time you modify the base `Button` class. Put simply, your GUI code has become awkwardly dependent on the volatile code of the business logic. + +And here’s the ugliest part. Some operations, such as copying/pasting text, would need to be invoked from multiple places. For example, a user could click a small “Copy” button on the toolbar, or copy something via the context menu, or just hit `Ctrl+C` on the keyboard. + +Initially, when our app only had the toolbar, it was okay to place the implementation of various operations into the button subclasses. In other words, having the code for copying text inside the `CopyButton` subclass was fine. But then, when you implement context menus, shortcuts, and other stuff, you have to either duplicate the operation’s code in many classes or make menus dependent on buttons, which is an even worse option. + +## Solution + +Good software design is often based on the principle of separation of concerns, which usually results in breaking an app into layers. The most common example: a layer for the graphical user interface and another layer for the business logic. The GUI layer is responsible for rendering a beautiful picture on the screen, capturing any input and showing results of what the user and the app are doing. However, when it comes to doing something important, like calculating the trajectory of the moon or composing an annual report, the GUI layer delegates the work to the underlying layer of business logic. + +In the code it might look like this: a GUI object calls a method of a business logic object, passing it some arguments. This process is usually described as one object sending another a request. + +The Command pattern suggests that GUI objects shouldn’t send these requests directly. Instead, you should extract all of the request details, such as the object being called, the name of the method and the list of arguments into a separate command class with a single method that triggers this request. + +Command objects serve as links between various GUI and business logic objects. From now on, the GUI object doesn’t need to know what business logic object will receive the request and how it’ll be processed. The GUI object just triggers the command, which handles all the details. + +The next step is to make your commands implement the same interface. Usually it has just a single execution method that takes no parameters. This interface lets you use various commands with the same request sender, without coupling it to concrete classes of commands. As a bonus, now you can switch command objects linked to the sender, effectively changing the sender’s behavior at runtime. + +You might have noticed one missing piece of the puzzle, which is the request parameters. A GUI object might have supplied the business-layer object with some parameters. Since the command execution method doesn’t have any parameters, how would we pass the request details to the receiver? It turns out the command should be either pre-configured with this data, or capable of getting it on its own. + +Let’s get back to our text editor. After we apply the Command pattern, we no longer need all those button subclasses + to implement various click behaviors. It’s enough to put a single field into the base `Button` class that stores a + reference to a command object and make the button execute that command on a click. + +You’ll implement a bunch of command classes for every possible operation and link them with particular buttons, depending on the buttons’ intended behavior. + +Other GUI elements, such as menus, shortcuts or entire dialogs, can be implemented in the same way. They’ll be linked to a command which gets executed when a user interacts with the GUI element. As you’ve probably guessed by now, the elements related to the same operations will be linked to the same commands, preventing any code duplication. + +As a result, commands become a convenient middle layer that reduces coupling between the GUI and business logic layers. And that’s only a fraction of the benefits that the Command pattern can offer! + +## How to Implement + +1. Declare the command interface with a single execution method. + +1. Start extracting requests into concrete command classes that implement the command interface. Each class must have a set of fields for storing the request arguments along with a reference to the actual receiver object. All these values must be initialized via the command’s constructor. + +1. Identify classes that will act as senders. Add the fields for storing commands into these classes. Senders should communicate with their commands only via the command interface. Senders usually don’t create command objects on their own, but rather get them from the client code. + +1. Change the senders so they execute the command instead of sending a request to the receiver directly. + +1. The client should initialize objects in the following order: + - Create receivers. + - Create commands, and associate them with receivers if needed. + - Create senders, and associate them with specific commands. \ No newline at end of file diff --git a/behavioral/command/__init__.py b/behavioral/command/__init__.py new file mode 100644 index 0000000..6e1c50e --- /dev/null +++ b/behavioral/command/__init__.py @@ -0,0 +1,5 @@ +""" +Turns a request into a stand-alone object that contains all information about +the request. This transformation lets you parameterize methods with different +requests, delay or queue a request’s execution, and support undoable operations. +""" diff --git a/behavioral/command/command.py b/behavioral/command/command.py new file mode 100644 index 0000000..558ffb3 --- /dev/null +++ b/behavioral/command/command.py @@ -0,0 +1,11 @@ +from abc import ABC, abstractmethod + + +class Command(ABC): + """ + The Command interface declares a method for executing a command. + """ + + @abstractmethod + def execute(self) -> None: + pass diff --git a/behavioral/command/commands.py b/behavioral/command/commands.py new file mode 100644 index 0000000..e950e1e --- /dev/null +++ b/behavioral/command/commands.py @@ -0,0 +1,41 @@ +from behavioral.command.command import Command +from behavioral.command.receiver import Receiver + + +class SimpleCommand(Command): + """ + Some commands can implement simple operations on their own. + """ + + def __init__(self, payload: str) -> None: + self._payload = payload + + def execute(self) -> None: + print( + f"SimpleCommand: See, I can do simple things like printing ({self._payload})") + + +class ComplexCommand(Command): + """ + However, some commands can delegate more complex operations to other + objects, called "receivers." + """ + + def __init__(self, receiver: Receiver, a: str, b: str) -> None: + """ + Complex commands can accept one or several receiver objects along with + any context data via the constructor. + """ + + self._receiver = receiver + self._a = a + self._b = b + + def execute(self) -> None: + """ + Commands can delegate to any methods of a receiver. + """ + + print("ComplexCommand: Complex stuff should be done by a receiver object", end="") + self._receiver.do_something(self._a) + self._receiver.do_something_else(self._b) diff --git a/behavioral/command/invoker.py b/behavioral/command/invoker.py new file mode 100644 index 0000000..d56e289 --- /dev/null +++ b/behavioral/command/invoker.py @@ -0,0 +1,38 @@ +from typing import Optional + +from behavioral.command.command import Command + + +class Invoker: + """ + The Invoker is associated with one or several commands. It sends a request + to the command. + """ + + _on_start: Optional[Command] = None + _on_finish: Optional[Command] = None + + """Initialize commands.""" + + def set_on_start(self, command: Command) -> None: + self._on_start = command + + def set_on_finish(self, command: Command) -> None: + self._on_finish = command + + def do_something_important(self) -> None: + """ + The Invoker does not depend on concrete command or receiver classes. The + Invoker passes a request to a receiver indirectly, by executing a + command. + """ + + print("Invoker: Does anybody want something done before I begin?") + if self._on_start: + self._on_start.execute() + + print("Invoker: ...doing something really important...") + + print("Invoker: Does anybody want something done after I finish?") + if self._on_finish: + self._on_finish.execute() diff --git a/behavioral/command/main.py b/behavioral/command/main.py new file mode 100644 index 0000000..00286c2 --- /dev/null +++ b/behavioral/command/main.py @@ -0,0 +1,16 @@ +""" +The client code can parameterize an invoker with any commands. +""" +from behavioral.command.commands import SimpleCommand, ComplexCommand +from behavioral.command.invoker import Invoker +from behavioral.command.receiver import Receiver + +if __name__ == '__main__': + invoker = Invoker() + invoker.set_on_start(SimpleCommand("Say hi")) + + receiver = Receiver() + invoker.set_on_finish(ComplexCommand( + receiver, "Send email", "Save report")) + + invoker.do_something_important() diff --git a/behavioral/command/receiver.py b/behavioral/command/receiver.py new file mode 100644 index 0000000..1a4d5be --- /dev/null +++ b/behavioral/command/receiver.py @@ -0,0 +1,12 @@ +class Receiver: + """ + The Receiver classes contain some important business logic. They know how to + perform all kinds of operations, associated with carrying out a request. In + fact, any class may serve as a Receiver. + """ + + def do_something(self, a: str) -> None: + print(f"\nReceiver: Working on ({a}).", end="") + + def do_something_else(self, b: str) -> None: + print(f"\nReceiver: Also working on ({b}).", end="")