Saturday, July 27, 2019

Command Pattern

Command pattern is a behavioral pattern. This pattern allows us to encapsulate a request(receiver, methods, arguments) inside an object, and thus client code can be made free of specific implementations of receiver types, thus decoupling the code.

Suppose we have a class with a method, say performAction(). This method receives an object as argument and has a basic responsibility of interacting with object. Object belongs to a group of objects. These objects get some work done, but unfortunately they don't have a common interface. Instead they have different set of methods to be invoked from performAction(). A very naive implementation of performAction() can be like:


In this implementation, client code is strongly coupled with specific implementation types rather than interface and hence it reduces code flexibility. If there are new types of objects in the system in future for with to deal with, this code would undergo change. This violates 'O' of 'SOLID' principles, and is a source of possible bugs.

Command pattern enables us to get rid of such code implementation and instead we encapsulate requests in objects we call as Command and client code then parametrize invoker(performAction()) with a Command object. We can now dynamically set the Command object, making performAction() interact with different types of object without having this sort of strongly coupled code.

Basically, when we want to decouple an object from making requests to objects which actually perform those requests, we use Command pattern.

Code structure



Command is an interface which has a single method, execute(). Client creates ConcreteCommand which is a specific implementation of Command, and sets Receiver in it. Receiver is the object which contains the actual actions. Invoker has a method setCommand() which Client uses to set Command. It also contains method performAction() which Client calls later on as needed. It is this method which invokes execute() on Command object.
ConcreteCommand basically represents the encapsulation of the request, as it contains the Receiver object and in its execute() it invokes all the required methods of Receiver needed to compete the command.
We can have different concrete Command implementations containing same type of Receiver. They would differ in their invocation of methods on Receiver.

Making use of Command pattern ensures that conditional code from invoker method is no more there and that logic is now expressed in form of different concrete command implementations.

Another way of looking at Command pattern is that it allows us to package a computation(receiver object and actions on it) in an object and pass it around as first class object. Hence, we can invoke computations away from its original point of creation, both in terms of time and space. For example, we can create it in one thread, move it around and then invoke it in another thread. This idea can be applied to many useful use cases like schedulers, thread pools, job queues.

For example, we can implement a queue to which we can add Command objects at one end, and on other end Threads can remove those objects one by one and call execute() method on them. Queue implementation in itself is totally decoupled from the computation aspects. It just acts as a conduit. Thread object just needs to invoke execute() on receiver and it has no other coupling with it. So as long as objects added to queue implement Command interface, we can be sure that execute() method would be invoked on them whenever a Thread is available to remove them from the queue.

Undoing

If our use case is to also support undoing what execute did, then we can add undo() method to Command interface, and in concrete implementation we can then call appropriate method on receiver object in undo().

Example


Runnable interface is a perfect example of Command pattern. Runnable interface corresponds to Command interface in UML diagram above. It has method run() which maps to execute(). We provide concrete implementation for Runnable which corresponds to ConcreteCommand in diagram.



Client code instantiates the concrete Runnable and handover it to a Thread which becomes the Invoker. Client can either set a Receiver explicitly in concrete Runnable or we can have the logic embedded inside run() itself. Thread's start method maps to performAction() in UML diagram above.



What we may not like


If there are many commands in the application, it would lead to too many command classes, and naturally more maintenance efforts.

No comments:

Post a Comment