Day 17: Command Pattern?—?Taking Orders and Executing Them Efficiently


Introduction

Welcome back, design pattern enthusiasts! Yesterday, we explored the Interpreter Pattern, empowering programs to understand external languages. Today, on Day 17 of our 30-Day Design Pattern Challenge, we’ll dive into the Command Pattern. This pattern offers a powerful way to encapsulate a request as an object, allowing for flexible queuing, logging, and undo/redo functionalities.

Problem

Imagine you’re developing a smart home system. Your task is to create a control panel with buttons for various operations, like turning lights on and off, adjusting the thermostat, and locking doors. You create a sleek Button class that can be used for buttons on the control panel, as well as for generic buttons in other parts of the app, like mobile or web interfaces.

All buttons in the app inherit from this base class. While they all look similar, each button needs to perform different actions. The simplest solution is to create many subclasses for each button, where each subclass contains the code to execute on a button click.

Lots of button subclasses. What can go?wrong?

Before long, you realize this approach is flawed. First, you end up with a huge number of subclasses, and making any change to the base Button class risks breaking the subclasses. Your GUI code becomes awkwardly dependent on the volatile business logic code.

Several classes implement the same functionality.

Some operations, like turning the lights on, need to be invoked from multiple places. For instance, you could click a small “Light On” button on the control panel, use a voice command via a smart speaker, or press a button on your mobile app.

Initially, when your app only had the control panel, it was okay to place the code for turning the lights on inside the LightOnButton subclass. But later, when you implement voice commands and mobile app buttons, you either have to duplicate the operation's code in many classes or make different interfaces dependent on the button subclasses, which is even worse.

Solution

Good software design often follows the principle of separation of concerns, breaking an app into layers. A typical example is having one layer for the graphical user interface (GUI) and another for the business logic. The GUI layer is responsible for displaying information on the screen and capturing user input. When it comes to performing important actions, like adjusting the thermostat or locking the doors, the GUI layer delegates the work to the underlying business logic layer.

In code, this might look like a GUI object calling a method of a business logic object, passing some arguments. This process is described as one object sending another a request.

The Command pattern suggests that GUI objects shouldn’t send requests directly. Instead, you should extract all request details (such as the target object, method name, and arguments) into a separate command class with a single method that triggers the request.

Command objects act as links between GUI and business logic objects. The GUI object doesn’t need to know what business logic object will handle the request or how it will be processed. It just triggers the command, which manages all the details.

Next, make your commands implement the same interface, usually with a single execute method that takes no parameters. This interface lets you use various commands with the same request sender without coupling it to concrete command classes. As a bonus, you can switch command objects linked to the sender, effectively changing the sender’s behavior at runtime.

You might wonder about request parameters. Since the command execute method doesn’t take parameters, how would you pass the request details to the receiver? The command should be either pre-configured with this data or capable of retrieving it on its own.

Applying the Command?Pattern

In our smart home system, after applying the Command pattern, you no longer need numerous button subclasses for different behaviors. Instead, put a single field in the base Button class to store a reference to a command object and make the button execute that command on a click.

You’ll implement various command classes for every possible operation and link them with specific buttons based on their intended behavior.

Other GUI elements, such as voice commands or mobile app buttons, can be implemented similarly. They’ll be linked to a command which gets executed when the user interacts with the GUI element. Elements related to the same operations will be linked to the same commands, preventing code duplication.

As a result, commands form a convenient middle layer that reduces coupling between the GUI and business logic layers. And that’s just one of the many benefits the Command pattern offers!

Class Diagram

The class diagram consists of the following entities

  • Command: This interface (or abstract class) defines the execution contract for all commands. It typically has an execute() method that performs the specific action.
  • Concrete Command: These classes implement the Command interface and encapsulate the logic for a specific action.
  • Invoker: This object holds and executes the Command objects. It can be a button, menu item, or any entity that triggers an action.
  • Receiver: This object actually performs the work upon receiving the command. It can be a separate class representing the functionality to be executed.
  • Client: This object creates concrete command objects and sets the receiver. It then passes the command to the invoker for execution.

Implementation Example

Let’s illustrate the Command Pattern with a simple light switch example.

interface Command {
  void execute();
}

class Light {
  public void turnOn() {
    System.out.println("Light turned on");
  }

  public void turnOff() {
    System.out.println("Light turned off");
  }
}

class LightOnCommand implements Command {
  private Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.turnOn();
  }
}

class LightOffCommand implements Command {
  private Light light;

  public LightOffCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.turnOff();
  }
}

class Switch {
  private Command command;

  public void setCommand(Command command) {
    this.command = command;
  }

  public void press() {
    if (command != null) {
      command.execute();
    }
  }
}        

Benefits of the Command?Pattern

  • Decoupling: Separates the request from the execution, promoting loose coupling and flexibility.
  • Queuing: Commands can be queued for later execution or batched for efficient processing.
  • Undo/Redo: Commands can be stored and replayed to implement undo/redo functionalities.
  • Logging: Commands can be logged for auditing or debugging purposes.
  • Macro Creation: Allows grouping multiple commands into a single macro for complex operations.

When to Use the Command?Pattern

  • You need to decouple the request from the execution.
  • You want to support queuing, logging, or undo/redo functionalities for actions.
  • Your system benefits from the ability to create macros or composite commands.

Real-World Examples

  • GUI Buttons and Menus: Buttons and menu items often trigger commands that interact with the application.
  • Macros: Macros in software programs can be implemented using the Command Pattern.
  • Transaction Processing Systems: Commands can be used to represent transactions and ensure atomicity.
  • java.lang.Runnable defines the interface implemented by classes whose instances are executed by threads.
  • Implementations of javax.swing.Action also conform to the command pattern.

Conclusion

The Command Pattern emerges as a versatile tool in a software developer’s arsenal, offering a structured approach to encapsulating and managing requests. By decoupling the request from its execution, you create systems that are more flexible, extensible, and maintainable. Whether you need to support undo/redo functionalities, queuing, or complex macro actions, the Command Pattern provides a robust foundation.

By mastering this pattern, you’ll gain the ability to design systems that are not only efficient but also adaptable to evolving requirements. So, embrace the power of the Command Pattern and elevate your software development practices!

Join me tomorrow as we explore another essential design pattern on our journey. Feel free to leave comments or questions below. If you enjoyed this blog, consider giving it a clap ??!

Stay tuned for Day 18!

References

RISHABH SINGH

Actively looking for Full-time Opportunities in AI/ML/Robotics | Ex-Algorithms & ML Engineer @ Dynocardia Inc | Computer Vision Research Assistant & Robotics Graduate Student @Northeastern University

7 个月

Very Informative ??

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了