JavaScript Design Patterns — Strategy and Decorator Pattern

John Au-Yeung - Jan 30 '21 - - Dev Community

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()
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .