DEV Community

Sota
Sota

Posted on

Command Pattern

What is Command Pattern?

Command pattern is a behavioral pattern that encapsulates a request as an independent object, thereby letting you pass requests as method arguments, queue or log requests, and support undoable operation.

When to use it?

  • Use Command pattern when you need to decouple an object making requests from the objects that know how to perform the requests.
  • Use Command pattern when you want to inject or assign different requests to objects at runtime.
  • Use Command pattern when you need undo or redo operation in your application.

Problem

Imagine we are designing API for a remote control that manipulates electronic devices in client house. Here we have lots of vender classes.

Image description

Introducing a common interface doesn't seem to be interesting since our vender classes are diverse, and it is also expected that we will have more vender classes.

Additionally, the remote control shouldn't consist of a set of if statements such as if slot1 == light, then light.turnOn(), else if slot1 == tv, then tv.turnOn() because it is a bad design.

Solution

Simply put, we want the remote control to know how to make a generic request but not to care about a specific vender class. To achieve this, we are going to separate our concern into two classes, one makes a request and another one actually performs the work to comply the request.

Image description

  1. RemoteControl
    This is the one that makes a request such as command.execute(), but doesn't care how to perform the work.
    If you want RemoteControl to have multiple slots—for example, one for OutdoorLight and another for TVRemoteControl can have arrays of commands, such as onCommands[] and offCommands[]. The setCommand method can take an integer to determine which slot to set. For instance, setCommand(2, TVonCommand, TVoffCommand) might assign TVonCommand to onCommands[2] and TVoffCommand to offCommands[2].

  2. Command
    Provides interface for all the ConcreteCommands.

  3. ConcreteCommand
    TurnOnOutdoorLightCommand object calls execute() method and delegates "how to turn on light" to the holding OurdoorLight object.

  4. OutdoorLight
    This is the one that knows how to perform the work to comply the request from RemoteControl.

  5. Client
    Client is responsible for creating ConcreteCommands and associates them with the corresponding Receivers.

Structure

Image description

Implementation in Java

// Receiver
public class OutdoorLight {

    public void turnOn() {
        System.out.println("Outdoor light is on");
    }

    public void turnOff() {
        System.out.println("Outdoor light is off");
    }
}
Enter fullscreen mode Exit fullscreen mode
public interface Command {

    void execute();

    void undo();
}
Enter fullscreen mode Exit fullscreen mode
// Concrete command
public class TurnOnOutdoorCommand implements Command {

    private OutdoorLight outdoorLight;

    public TurnOnOutdoorCommand(OutdoorLight outdoorLight) {
        this.outdoorLight = outdoorLight;
    }

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

    @Override
    public void undo() {
        outdoorLight.turnOff();
    }
}
Enter fullscreen mode Exit fullscreen mode
// Concrete command
public class TurnOffOutdoorLightCommand implements Command {

    OutdoorLight outdoorLight;

    public TurnOffOutdoorLightCommand(OutdoorLight outdoorLight) {
        this.outdoorLight = outdoorLight;
    }

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

    @Override
    public void undo() {
        outdoorLight.turnOn();
    }
}
Enter fullscreen mode Exit fullscreen mode
// Null Object
public class NoCommand implements Command {

    @Override
    public void execute() {
    }

    @Override
    public void undo() {
    }
}
Enter fullscreen mode Exit fullscreen mode
// Invoker
public class RemoteControl {

    private Command onCommand;
    private Command offCommand;
    public Stack<Command> undoCommands;

    public RemoteControl() {
        onCommand = new NoCommand();
        offCommand = new NoCommand();
        undoCommands = new Stack<>();
        undoCommands.push(new NoCommand());
    }

    public void setCommand(Command onCommand, Command offCommand) {
        this.onCommand = onCommand;
        this.offCommand = offCommand;
    }

    public void onButtonWasPushed() {
        onCommand.execute();
        undoCommands.push(onCommand);
    }

    public void offButtonWasPushed() {
        offCommand.execute();
        undoCommands.push(offCommand);
    }

    public void undoButtonWasPushed() {
        if (!(undoCommands.peek() instanceof NoCommand)) {
            Command lastCommand = undoCommands.pop();
            lastCommand.undo();
        } else {
            System.out.println("undoCommands stack is empty!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
public class Client {

    public static void main(String[] args) {
        OutdoorLight outdoorLight = new OutdoorLight(); // Create a receiver

        // Create commands on that receiver
        // These commands are encapsulated requests in other words
        Command turnOnOutdoorLight = new TurnOnOutdoorCommand(outdoorLight);
        Command turnOffOutdoorLight = new TurnOffOutdoorLightCommand(outdoorLight);

        RemoteControl rc = new RemoteControl(); // Create an invoker
        // We can pass encapsulated requests to other object
        rc.setCommand(turnOnOutdoorLight, turnOffOutdoorLight); // Set command on that invoker

        rc.undoButtonWasPushed();
        rc.onButtonWasPushed();
        rc.offButtonWasPushed();
        rc.undoButtonWasPushed();
        rc.undoButtonWasPushed();
        rc.undoButtonWasPushed();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

undoCommands stack is empty!
Outdoor light is on
Outdoor light is off
Outdoor light is on
Outdoor light is off
undoCommands stack is empty!
Enter fullscreen mode Exit fullscreen mode

NoCommand is know as Null Object which is another design pattern. It is useful when you don't have a meaning object to return. Here we use it as default command that Invoker holds so that we don't need to handle null.

Pitfalls

  • Client needs to do lots of things such as creating ConcreteCommands & setting corresponding Receivers, creating Invoker & setting ConcreteCommands on that Invoker.

MacroCommand

MacroCommand is a simple extension of Command pattern that allows you to have one command that turns on a light, TV, AC, coffee machine and plays music.
Let's make a new kind of Command that executes a set of other Commands.

public class MacroCommand implements Command {

    Command[] commands;

    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    // Executes holding commands one at a time
    @Override
    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Now we're ready to use our MacroCommand. But before that, let me show you how to implement RemoteControl that can have multiple slots as I mentioned in "Solution" section.

public class RemoteControl {

    private Command[] onCommands;
    private Command[] offCommands;

    public RemoteControl() {
        // Our RemoteControl has 3 slots
        onCommands = new Command[3];
        offCommands = new Command[3];

        Command noCommand = new NoCommand();
        for (int i = 0; i < onCommands.length; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }

    public void setCommands(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }

    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }

    // Print slots info in pretty style
    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("\n------ Remote Control ------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuilder.append("[slot " + i + "] " + onCommands[i].getClass().getSimpleName() +
                    "    " + offCommands[i].getClass().getSimpleName() + "\n");
        }
        return stringBuilder.toString();
    }
}
Enter fullscreen mode Exit fullscreen mode

Pretty simple, just use array to store multiple commands.
Finally, Client uses our MacroCommand. For the sake of simplicity, I created only two receivers, Light and CoffeeMachine.

public class Client {

    public static void main(String[] args) {
        // Create receivers
        Light light = new Light();
        CoffeeMachine coffeeMachine = new CoffeeMachine();

        // Create commands and set corresponding receivers on them
        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);
        Command coffeeMachineOnCommand = new CoffeeMachineOn(coffeeMachine);
        Command coffeeMachineOffCommand = new CoffeeMachineOff(coffeeMachine);

        // Create arrays of commands
        Command[] morningOn = {lightOnCommand, coffeeMachineOnCommand};
        Command[] morningOff = {lightOffCommand, coffeeMachineOffCommand};

        // Create macros
        MacroCommand morningOnMacro = new MacroCommand(morningOn);
        MacroCommand morningOffMacro = new MacroCommand(morningOff);

        RemoteControl rc = new RemoteControl();
        rc.setCommands(0, morningOnMacro, morningOffMacro);

        System.out.println(rc);

        System.out.println("--- Pushing Macro On ---");
        rc.onButtonWasPushed(0);
        System.out.println("\n--- Pushing Macro Off ---");
        rc.offButtonWasPushed(0);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

------ Remote Control ------
[slot 0] MacroCommand    MacroCommand
[slot 1] NoCommand    NoCommand
[slot 2] NoCommand    NoCommand

--- Pushing Macro On ---
Light is on
Coffee machine is on
Coffee machine is making a cup of coffee

--- Pushing Macro Off ---
Light is off
Coffee machine is off
Enter fullscreen mode Exit fullscreen mode

Our MacroCommand turns on the light and starts the coffee machine, which immediately begins brewing coffee. Note that we can define a set of actions in the Receiver and use them in the execute() method of the ConcreteCommand, as shown in the UML diagram in the "Structure" section.

@Override
    public void execute() {
        coffeeMachine.on();
        coffeeMachine.makeCoffee();
    }
Enter fullscreen mode Exit fullscreen mode

You can check all the design pattern implementations here.
GitHub Repository


P.S.
I'm new to write tech blog, if you have advice to improve my writing, or have any confusing point, please leave a comment!
Thank you for reading :)

Top comments (0)