Design Pattern : Command

Design Pattern : Command

Design Patterns: Elements of Reusable Object-Oriented Software

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

What is Command Design Pattern?

** Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.**

Communication between objects in enterprise applications starts from an object sending a request to another object. The object receiving the request can either process the request and send back a response or forward the request to another object for processing. Typically requests are made by the invoker (the object making the request) through method calls on the object that processes the request, which we will refer as the receiver. As a result, the invoker and the receiver are tightly coupled. This violates the SOLID design principles that advocates loosely coupled components to ensure that changes made to one component does not affect the other components of the application.

Let's think in terms of a "Switch". When a invoker invokes a command, it might start a light or stop a light or a generator. The same invoker (switch) can be used to invoke commands to perform actions on different receivers (devices). We only need to create the appropriate command object and set it on the invoker.

Let’s start with an analogy of remote-controlled toys.

A toy manufacturating company provide remote-controlled toys. They started with a remote-controlled car with On, Off, Undo features. Then remote-controlled rotating top, after that more 200+ toys with same features. Writing individual features for indiviual toys is costly, redundant, tightly coupled and unable to re-use. That's where Command Pattern plays it's rules.

Let’s summarize the participants of the Command Pattern.

  • Command (Command): Is an interface for executing an action.

  • ConcreteCommand (CarMoveCommand, CarStopCommand, TopRotateCommand, and TopStopRotateCommand): Are concrete classes that implements Command and defines the execute() and undo() methods to communicate with receivers for performing an action and undoing it respectively.

  • Invoker(BaseRemoteControl, RemoteControl): Asks Command to carry out the action.

  • Receiver (Car and RotatingTop): Performs the action based on the command it receives.

  • Client: Creates a ConcreteCommand object and sets its receiver.

Lets get into the coding part

interface Command {
    fun execute()
    fun undo()
}

Toy Objects with some features:

class Car {
    fun move() = println("Car is moving")
    fun stop() = println("Car has stopped")
}
class RotatingTop {
    fun startRotating() = println("Top has start rotating")
    fun stopRotating() = println("Top has stopped rotating")
}

Concrete Command Objects with implementations of execute() and undo() according to Toys functionality.

class CarMoveCommand(private val car: Car) : Command {
    override fun execute() {
        println("CarMoveCommand.execute(): Invoking move() on Car")
        car.move()
    }
    override fun undo() {
        println("CarMoveCommand.undo():  Undoing previous action->Invoking stop() on Car")
        car.stop()
    }
}

class CarStopCommand(private val car: Car) : Command {
    override fun execute() {
        println("CarStopCommand.execute(): Invoking stop() on Car")
        car.stop()
    }
    override fun undo() {
        println("CarStopCommand.undo(): Undoing previous action-> Invoking move() on Car")
        car.move()
    }
}


class TopRotateCommand(private var rotatingTop: RotatingTop) : Command {
    override fun execute() {
        println("TopRotateCommand.execute(): Invoking startRotating() on RotatingTop")
        rotatingTop.startRotating()
    }
    override fun undo() {
        println("TopRotateCommand.undo(): Undoing previous action->Invoking stopRotating() on RotatingTop")
        rotatingTop.stopRotating()
    }
}

class TopStopRotateCommand(private var rotatingTop: RotatingTop) : Command {
    override fun execute() {
        println("TopStopRotateCommand.execute(): Invoking stopRotating() on RotatingTop")
        rotatingTop.stopRotating()
    }
    override fun undo() {
        println("TopStopRotateCommand.undo(): Undoing previous action->Invoking startRotating() on RotatingTop")
        rotatingTop.startRotating()
    }
}

Remote Control provides access to On, Off, Undo operations to the clients:

interface BaseRemoteControl {
    fun onButtonPressed(onCommand: Command)
    fun offButtonPressed(offCommand: Command)
    fun undoButtonPressed()
}

class RemoteControl : BaseRemoteControl {
    private var onCommand: Command? = null
    private var offCommand: Command? = null
    private var undoCommand: Command? = null
    override fun onButtonPressed(onCommand: Command) {
        this.onCommand = onCommand
        onCommand.execute()
        undoCommand = onCommand
    }
    override fun offButtonPressed(offCommand: Command) {
        this.offCommand = offCommand
        offCommand.execute()
        undoCommand = offCommand
    }
    override fun undoButtonPressed() {
        undoCommand?.undo()
    }
}

Let's test our RemoteControl

class CommandPatternExample {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val remoteControl: BaseRemoteControl = RemoteControl()
            println("-----Testing onButtonPressed on RemoteControl for Car-----")
            val car = Car()
            val carMoveCommand: Command = CarMoveCommand(car)
            remoteControl.onButtonPressed(carMoveCommand)
            println("-----Testing offButtonPressed on RemoteControl for Car-----")
            val carStopCommand: Command = CarStopCommand(car)
            remoteControl.offButtonPressed(carStopCommand)
            println("-----Testing undoButtonPressed() on RemoteControl for Car-----")
            remoteControl.undoButtonPressed()
            println("-----Testing onButtonPressed on RemoteControl for RotatingTop-----")
            val top = RotatingTop()
            val topRotateCommand: Command = TopRotateCommand(top)
            remoteControl.onButtonPressed(topRotateCommand)
            println("-----Testing offButtonPressed on RemoteControl for RotatingTop-----")
            val topStopRotateCommand: Command = TopStopRotateCommand(top)
            remoteControl.offButtonPressed(topStopRotateCommand)
            println("-----Testing undoButtonPressed on RemoteControl for RotatingTop-----")
            remoteControl.undoButtonPressed()
        }
    }
}

Result:

-----Testing onButtonPressed on RemoteControl for Car-----
CarMoveCommand.execute(): Invoking move() on Car
Car is moving

-----Testing offButtonPressed on RemoteControl for Car-----
CarStopCommand.execute(): Invoking stop() on Car
Car has stopped

-----Testing undoButtonPressed() on RemoteControl for Car-----
CarStopCommand.undo(): Undoing previous action-> Invoking move() on Car
Car is moving

-----Testing onButtonPressed on RemoteControl for RotatingTop-----
TopRotateCommand.execute(): Invoking startRotating() on RotatingTop
Top has start rotating

-----Testing offButtonPressed on RemoteControl for RotatingTop-----
TopStopRotateCommand.execute(): Invoking stopRotating() on RotatingTop
Top has stopped rotating

-----Testing undoButtonPressed on RemoteControl for RotatingTop-----
TopStopRotateCommand.undo(): Undoing previous action->Invoking startRotating() on RotatingTop
Top has start rotating

Process finished with exit code 0

Full implementation in Kotlin