When a design pattern goes well hand in hand with the state pattern, it is arguably the command pattern.
If you read one of my previous blog posts about the State pattern, you may remember this sentence: “The State pattern ensures that an object can execute predictable, coordinated methods based on the current “state” of the application. deals with.”
In the command pattern, the main goal is to separate communication between two important participants:
- Initiator (also called Invoker)
We will cover Command Pattern and State Pattern in this post. If you are learning either one, my best advice to get the most out of this post is to make sure you understand the flow of the state pattern before continuing with the implementation of the command pattern so that To get a better feel for how the behavior of the code changes drastically while maintaining functionality.
Let’s start with an example using the State pattern to see this more clearly. Here’s the code:
In the above example, we have a
subscribers The object holds a collection of callback functions. These callback functions are called whenever
setState function is called. Here’s what it looks like:
Every time the state updates, all registered callbacks are called with the previous state (
prevState) and new State (
newState) in arguments.
We have registered a callback listener so that we can see the state updates and update the background color whenever the number of profiles is a certain
length, The table below shows a clear picture of lining up the count of the profile with the respective colour:
Minimum ThresholdBackground Color0white5blue9orange10red
Note: Code here is taken from previous code block
if (length >= 5 && length <= 8) return 'blue' // Average
if (length > 8 && length < 10) return 'orange' // Reaching limit
if (length > 10) return 'red' // Limit reached
return 'white' // Default
return (prevState, newState) => 
const newProfiles = newState?.profiles
So, how can the command pattern fit into this? If we look at our code, we can see that we have defined some functions responsible for calling and handling this logic. It looks like this:
Instead we can abstract these into commands. The upcoming code example will show similar code with the command pattern applied corresponding to the state pattern. When we do this, only two functions are left untouched:
Let’s go ahead and introduce the command pattern and command our abstract functions like this:
It is now very clear to determine which functions we need to update the state. We can separate everything into their separate command handlers. This way, we can separate them into their own separate file or location to work with them more easily.
Here are the steps shown in our updated example to get to that point:
commandsChar. It will store the registered commands and their callback handlers.
registerCommand, This will register new commands and their callback handlers
dispatch, It is responsible for calling the callback handler associated with their command.
With these three steps, we are all set up to have our commands registered by the client code, allowing them to implement their own commands and logic. notice how our
dispatch Functions don’t need to be aware of anything related to our state objects.
We can easily take advantage of this and keep separating them into a separate file:
As per the actual logic written in these lines, here is what it looks like:
Normally, this would all be left to the client code to decide (technically, our last code snippet represents client code). Some library authors also define their own command handlers for internal use, but the same concept applies.
A practice I often see is putting their internal logic in a separate file, prefixed with the filename of
internalCommands.ts, Worth noting that 99% of the time, the functions in those files are never exported to the user. That’s why they are marked internal.
The image below is a diagram of what our code looks like before we apply the Command Design Pattern:
Purple colored bubbles represent functions. two tasks
addProfile are included in them. specifically for both of them, however, call
setState To facilitate the change of state directly. In other words, they call and handle the state update logic for the specific slice of state they are interested in.
Now look at the diagram below. This image shows how our code looks like after applying the pattern:
setBackgroundColor are gone, but all their arguments remain. They are now written as command handlers:
Command handlers define their arguments separately and get registered. Once they are registered, they are “put on hold” until they are called by
Ultimately, the functionality of the code remains the same, and only the behavior changes.
In Lexical, the commands for the editor can be Lodged and becomes available for use. handling logic is defined when they are registered so that they can be recognized for
I hope you find it valuable. See more in the future!