Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
Design patterns are the basis of any good software. JavaScript programs are no exception.
In this article, we’ll look at the strategy and decorator design patterns.
Strategy Pattern
The strategy design pattern is a design pattern that lets us select from a family of algorithms during run time.
The algorithms should be substitutable with each other.
This pattern’s main idea is to let us create multiple algorithms for solving the same problem.
We have one object that does things one way and another that does things another way.
There may be more than one way to do the same thing.
For instance, we may have one function that calls a function depending on what we want to do.
We may write:
const smartStrategy = () => {
//..
}
const smarterStrategy = () => {
//..
}
const dumbStrategy = () => {
//..
}
const doTask = () => {
if (shouldBeSmart) {
smartStrategy()
} else if (shouldBeSmarter) {
smarterStrategy()
} else if (shouldBeDumb) {
dumbStrategy()
}
}
We have the doTask
function that runs the functions that have the strategies that we want to use to accomplish a task.
This way, we can pick the algorithm that suits the task the most.
This can be from a user setting or it can be chosen automatically.
The whole idea is to define a family of algorithms, encapsulate each one, and make them interchangeable.
Making them interchangeable is important since we want them to accomplish the same results at the end.
The strategy pattern is good for separating volatile code from a part of the program so that the part that changes are separate from the stable code.
Also, using the strategy pattern, we don’t have to split the implementation code over several inherited classes as often.
Using this pattern reduces the chance of having many child classes for one parent class.
This is a simple pattern that let us choose different ways to do the same thing and reduce the risk of volatile code that break things by separating them out.
Algorithms are encapsulated by one interface that doesn’t change so that outside code can just use that instead of changing the inner workings of the code all the time.
Closed for Modification, Open for Extension
In the same vein, once we wrote some code, we shouldn’t be modifying them too often.
Instead, they should be open to extensions so that we can add capabilities to them later.
Changing code always introduces risks. The more changes we make, the higher the chance that we break things.
Therefore, we should just extend things as much as possible so that the existing code stays untouched.
Decorator Pattern
The decorator pattern is a pattern where we write wrapper code to let us extend the core code.
We just keep wrapping objects around objects that we want to use so that we keep extending the capabilities of the existing objects by defining new objects that have the capabilities of the existing object.
For instance, we can write:
const computer = {
//...
description() {
//...
}
};
const disk = {
///...
computer,
description() {
//...
}
}
const laptop = {
///...
disk
}
The computer
object has the core capabilities of a computer.
Then we extend the capabilities of disk
with by putting the computer
object inside the disk
object.
Then we create a laptop
object, that has the disk
object, which has the computer
object.
Now we created extensions computer
, which are disk
and laptop
, without modifying the computer
object itself.
That’s a safe way to add capabilities to computer
without the risks of modifying computer
directly.
Modifying things directly may break the thing itself or the things that use it.
Likewise, we can use the same pattern as an object but replacing them with classes.
For instance, we can write:
class Computer {
//...
description() {
//...
}
}
class Disk {
constructor() {
this.computer = new Computer();
}
//...
description() {
//...
}
}
const laptop = {
constructor() {
this.disk = new Disk();
}
//...
}
We nest the Disk
and Computer
instances within Laptop
so we can use them.
Conclusion
The strategy pattern lets us use different ways of solving the same problem by encapsulating them within one entity that can be invoked directly.
With this pattern, the strategies themselves are hidden from the outside.
With the decorator pattern, we can extend the capabilities of things by creating new things rather than changing things directly.
This reduces the chance of breaking things.