JavaScript Design Patterns in Action

JavaScript Design Patterns in Action

JavaScript Design Patterns in Action

In this article, we are going to explore all the popular design patterns used in JavaScript. We will discuss how to implement these patterns and why we need to build them.

We will discuss:

  1. Creational Patterns
    • Factory pattern
    • Abstract factory pattern
    • Prototype pattern
    • Builder pattern
  2. Structural Patterns
    • Adapter Pattern
    • Decorator
    • Composite
  3. Behavioral Patterns
    • Chain of Responsibility
    • Observer
    • Mediator

By the end of this article, you will have a better understanding of the design patterns and when and why to apply them. Let’s dive in 🔥

🏭Factory pattern

This is one of the very basic patterns in JavaScript. You may already be using this pattern without knowing about it. In factory patterns, we define a function (a factory function) that is responsible for creating similar objects.

Let’s elaborate on this with a simple example. Let’s say we are building an application for a delivery company. So whenever a new delivery request is created we handle that in our application like this:

It is a very basic class. We pass in two parameters, the address and the delivery item, and create a new instance of that class. So let’s say when we started our business, we were only delivering by bikes. Now after a couple months, our user base has grown: we have more clients and need to deliver to distant places. So we’ve decided that based on the address — if the distance is more than 10 km, we will deliver by car, and if the distance is more than 50 km, we will deliver by truck. Now the problem is how we encapsulate our business logic so that it is decoupled and in the future, we can introduce new changes as easily as necessary. For instance, maybe in the future, we want to add the delivery by air or by sea options.

🏭Factory Pattern to rescue….

We can create a factory method which will dynamically create the delivery instance. Let’s take a look at the code below.

Above we have our factory function that calculates the distance first and then initiates a proper delivery class instance. We have our different delivery method classes declared like this

We can create the delivery instance through the factory method like below.

This allows us to encapsulate our delivery logic. Our code is now decoupled. We can add new delivery classes (i.e. delivery by air or delivery by sea) if we want to without touching the existing logic. We can also introduce changes easily if we have to. For example, let’s say we want to add additional fees if it is delivered by truck. We can just add that to the truck class without worrying about breaking anything.

💡 Would you like to know how to implement factory pattern in React.js? Head over to the this article

Factory Pattern

Factory Pattern

above is the visualization for factory pattern.

Abstract Factory

We can take the idea of a factory pattern one step further. We can introduce another layer of abstraction around the factory method. But why would we want to do that?

Let’s imagine our business logic has become more complex. We added two new features. The first is the same day delivery and second is express delivery. Same day delivery is only available for some address, so now we have to check for that logic and express delivery is only available by truck and air. So we would want to add another layer of abstraction around our factory.

Let’s take a look at the diagram below.

Abstract Factory

Abstract Factory

We will have an abstract factory function that will call our factory functions and factories will do the instantiations.

Let’s take a look at the code to implement this

sameDayDeliveryFactory and expressDeliveryFactory are other factory functions that create and return an appropriate instance of the class.

🤖Prototype pattern

JavaScript language is based on prototype-based inheritance. Therefore prototype pattern is one of the most commonly used patterns. Chances are you are already using it. In a prototype pattern, we create new instances of objects by cloning them from a prototype. We also add new properties to that newly created object in run-time. Let’s elaborate on this with a simple example.

We have a vehicle object. Which is our prototype. We create car and car2 instances of that object and we automatically inherit a vehicle’s behavior. The code above outputs the following.

Code Output

Code Output

👷 Builder Pattern

This creational pattern is used to build complex objects step by step. This pattern allows you to create a different representation of an object using the same class constructor. This pattern is more common in pure OOP languages such as Java and C#.

Let’s look at a scenario to better understand why we would need something like a builder pattern. Let’s say we have an application where a user can have a customized profile. We will give the user the ability to change certain attributes like

  • Menu location
  • Profile Picture
  • Theme
  • Menu Items

So to do this we will have a class that looks like this

This works. However, soon we realize that we need to pass more arguments to our constructor because our app is getting popular, and users want more customization.

Now we are passing in too many arguments in our Profile class as it grows. This will result in a messy code that is hard to test and prone to errors.

To avoid such situation we create a builder class

We will also change our Profileclass to accept a builder instance instead of each argument.

Now we can explicitly call the builder class for our object creation and we can customize it as we want.

And that’s it. Builder pattern gives us a nice abstraction layer, as our application grows we require to create more custom objects and test them. This pattern makes that easier.

🔌Adapter Pattern

This pattern is used to convert one interface to another. Let’s say we have a flight ticket booking application. There are existing customers that are already using this application. So our old interface looks like this

Now let’s say we want to add a new feature that calculates our ticket prices with a discount added feature. We want to give this discount to certain users. So some of our users will have to use the new interface.

We don’t want the client application to make any changes to their API. How do we apply this new interface to the existing API? We use an adapter interface. Let’s take a look at the code below.

Rather using the new interface directly we create an adapter so that the client program can work without any significant changes.

🎍Decorator

In classical object-oriented programing, this pattern is used to give individual classes additional functionality. In JavaScript, we can apply the decorator pattern using higher-order functions.

ℹ️ P.S. Don’t confuse decorator patterns with Typescript/ES7 decorators. As these topics are beyond the scope of this article we will not be discussing them.

Let’s say we have a sandwich shop application. We have the following code for our sandwiches

Now once in a while, some clients order footlong. In our application, 6 inches is the default size of the sandwich. Some of the property of the sandwich needs to be changed but not all of them.  In this case, we want to avoid code rewrites and use our existing codebase as much as possible. So we create a decorator function that adds in the additional logic like below.

We can have any number of custom objects. For example, if we need to create a footlong beef sandwich in the future, we will just create another decorator function. Take a look at the code below.

With our decorators, we have created a pattern that allows us to keep our code DRY (Do not repeat yourself). We are able to reuse our code through functional composition.

🌳Composite

Composite pattern is used to structure our code into a tree-like hierarchy. We have a root node which is our base object or class. This node will have many children which will inherit the parent functionality and add additional functionality to it. Children can have their own children to further enhance the functionality.

It may sound confusing at first. Let’s take a look at the structure for our imaginary application.

We will have a base class called vehicle and we will sub children that will inherit the vehicle properties and then override some of the properties.

So here we got our vehicle class. Which is the root node in our composite tree? Now let’s say we would like to have a car, truck and SUV classes with slight modifications. We can implement that with es6 extendskeyword and make a composite structure.

As you can see, we are overriding some of the behaviors of the parent and adding additional functionality. You can keep creating children like so with more complex functionality. The main benefit of having a composite pattern is that as our application grows we can easily create a complex entity from the existing one.

⛓️Chain of Responsibility

This is a common pattern in Node.js. If you have used Node.js middleware then you might have already been using it. The idea of this pattern is that a payload should not care about which function it will get processed by rather it will go through a chain of functions and appropriate function will process it.

Chain of Responsibility

Chain of Responsibility

Let’s take a look at a simple example.

Above we have 3 process functions declared. We then put them in a chain array.

Next, we will create a processRequest function and pass this chain with a sample request.

As we can see this is very similar to Node.js middleware pattern. When we are dealing with asynchronous data (i.e. express.js middleware) this pattern comes in handy.

🕵️Observer

One of the very popular patterns in ES6/ES7 is an observer pattern. In this pattern, an object (known as the subject) maintains a list of other objects (observers) depending on it, automatically notifying them of any changes of the state. Reactive frameworks like React.js, Vue.js are based on this pattern. We will implement a simple example of an observer.

Imagine we have an application where we have to update multiple elements in the DOM when an event occurs (i.e. user input, mouse clicks). Let’s say in our application we would like to change the state of A, B and C when the user starts inputting in the field. So we can have an Observable class and have A, B and C subscribe to it. So A, B, C will be listening for any changes and updated their states accordingly. Let’s say after user types in the first 3 characters in the input field we don’t want to change C. We want to stop listening to that event for C, in that case, we should also have an unsubscribe function in our Observable class. That way we can Let’s write our Observable class first.

Now we can create a couple DOM references

With these DOM references, we can create a couple of observers

Then instantiate a new observable instance and subscribe to these observers

This is a simplified version of an observer pattern. The concept of reactive programming is based on this pattern. Libraries like React.js, RxJs follows an observer pattern.

💡Want to learn more about Reactive Programming? Check this article out

👉How I reversed engineered RxJs and learned reactive programing

⚽Mediator

Our applications are made up of many classes. As our application becomes larger we add more of these classes and communication between these classes may become complex. This makes the code harder to read, test and maintain. With mediator pattern classes communicate through a mediator object. This way classes don’t depend on each other directly which lowers the coupling.

Let’s say we are building a chat room application (i.e. slack). Participants can join the chatroom by registering to the room. Participants can send messages to each other. The chatroom object in this application will work as a mediator. Each participant is represented by a participant object. The participant only sends the message and the Chatroom handles routing.

Let’s take a look at the code

Conclusion

We have looked at some of the commonly used design patterns in JavaScript and their simple implementations. If you would like to take a step further, dig deeper into this topic and learn some of the other design patterns, the following books might help in your journey.

https://addyosmani.com/resources/essentialjsdesignpatterns/book/

http://shop.oreilly.com/product/9780596007126.do

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

26.03.2024

An Introduction to Clustering in Node.js

Picture your Node.js app starts to slow down as it gets bombarded with user requests. It's like a traffic jam for your app, and no developer likes ...

15.03.2024

JAMstack Architecture with Next.js

The Jamstack architecture, a term coined by Mathias Biilmann, the co-founder of Netlify, encompasses a set of structural practices that rely on ...

Rendering Patterns: Static and Dynamic Rendering in Nextjs

Next.js is popular for its seamless support of static site generation (SSG) and server-side rendering (SSR), which offers developers the flexibility ...

3 comments

Nikita Bragin January 3, 2020 at 12:17 am
0

Hi, Shadid, please check comments on Reddit topic https://www.reddit.com/r/javascript/comments/eiv9x2/javascript_design_patterns_in_action/
Seems need to update code samples

 
Shadid Haque January 3, 2020 at 1:13 am
0

hi Nikita, I have replied to the reddit comments. Object.create() and Object.assign() are very similar, they both copy the old object to new a new one. Object.assign() is just more flexible as it acts as enumerable and can be used to combine multiple objects. Again this is a simple preference one can totally use Object.create(). perhaps I will make a short article describing why they exist and when we prefer one over other?

 
 
Nikita Bragin January 3, 2020 at 1:18 am
0

Great, thank you! Regarding the new article please discuss with Marina, she is responsible for the blog’s stuff.

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy