Design Pattern : Command
Design Patterns: Elements of Reusable Object-Oriented Software

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
Search for a command to run...
Design Patterns: Elements of Reusable Object-Oriented Software

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
No comments yet. Be the first to comment.
A practical guide to choosing the right coroutine primitive for your Android/Kotlin projects

This guide explains what Channels are, when to use them, how to use them correctly, and when NOT to use them β in simple, professional language. 1. What problem do Channels solve? In Kotlin coroutines, you often have multiple coroutines running at t...

Learn how to build preview-safe ViewModels in Jetpack Compose using Hilt.

HandlerInterceptor vs Filter in Spring Boot: A Practical Guide with JNDI Injection Prevention

Simple In-Memory Caching: A Tiny Trick with Massive Impact

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
** 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.
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