JavaScript Design Patterns – Explained with Examples
https://www.freecodecamp.org/news/javascript-design-patterns-explained/
Last updated
https://www.freecodecamp.org/news/javascript-design-patterns-explained/
Last updated
Hi everyone! In this article I'll explain what design patterns are and why they're useful.
We'll also go through some of the most popular design patterns out there and give examples for each of them. Let's go!
Design patterns were popularized by the book "Design Patterns: Elements of Reusable Object-Oriented Software", published in 1994 by a group of four C++ engineers.
The book explores the capabilities and pitfalls of object-oriented programming, and describes 23 useful patterns that you can implement to solve common programming problems.
These patterns are not algorithms or specific implementations. They are more like ideas, opinions, and abstractions that can be useful in certain situations to solve a particular kind of problem.
The specific implementation of the patterns may vary depending on many different factors. But what's important is the concepts behind them, and how they might help us achieve a better solution for our problem.
This being said, keep in mind these patterns were thought up with OOP C++ programming in mind. When it comes to more modern languages like JavaScript or other programming paradigms, these patterns might not be equally useful and might even add unnecessary boilerplate to our code.
Nevertheless, I think it's good to know about them as general programming knowledge.
Side comment: If you're not familiar with programming paradigms or OOP, I recently wrote two articles about those topics. 😉
Anyway... Now that we've gotten the introduction out of the way, design patterns are classified into three main categories: creational, structural, and behavioral patterns. Let's briefly explore each of them. 🧐
Creational patterns consist of different mechanisms used to create objects.
Singleton is a design pattern that ensures that a class has only one immutable instance. Said simply, the singleton pattern consists of an object that can't be copied or modified. It's often useful when we want to have some immutable single point of truth for our application.
Let's say for example we want to have all of our app's configuration in a single object. And we want to disallow any duplication or modification of that object.
Two ways of implementing this pattern are using object literals and classes:
Using an object literal
Using classes
The Factory method pattern provides an interface for creating objects that can be modified after creation. The cool thing about this is that the logic for creating our objects is centralized in a single place, simplifying and better organizing our code.
This pattern is used a lot and can also be implemented in two different ways, via classes or factory functions (functions that return an object).
Using classes
Using a factory function
The Abstract Factory pattern allows us to produce families of related objects without specifying concrete classes. It's useful in situations where we need to create objects that share only some properties and methods.
The way it works is by presenting an abstract factory the client interacts with. That abstract factory calls the corresponding concrete factory given the corresponding logic. And that concrete factory is the one that returns the end object.
Basically it just adds an abstraction layer over the factory method pattern, so that we can create many different types of objects, but still interact with a single factory function or class.
So let's see this with an example. Let's say we're modeling a system for a car company, which builds cars of course, but also motorcycles and trucks.
The Builder pattern is used to create objects in "steps". Normally we will have functions or methods that add certain properties or methods to our object.
The cool thing about this pattern is that we separate the creation of properties and methods into different entities.
If we had a class or a factory function, the object we instantiate will always have all the properties and methods declared in that class/factory. But using the builder pattern, we can create an object and apply to it only the "steps" we need, which is a more flexible approach.
This is related to object composition, a topic I've talked about here.
The Prototype pattern allows you to create an object using another object as a blueprint, inheriting its properties and methods.
If you've been around JavaScript for a while, you're probably familiar with prototypal inheritance and how JavaScript works around it.
The end result is very similar to what we get by using classes, but with a little more flexibility since properties and methods can be shared between objects without depending on the same class.
Structural patterns refer to how to assemble objects and classes into larger structures.
The Adapter allows two objects with incompatible interfaces to interact with each other.
Let's say, for example, that your application consults an API that returns XML and sends that information to another API to process that information. But the processing API expects JSON. You can't send the information as it's received since both interfaces are incompatible. You need to adapt it first. 😉
We can visualize the same concept with an even simpler example. Say we have an array of cities and a function that returns the greatest number of habitants any of those cities have. The number of habitants in our array is in millions, but we have a new city to add that has its habitants without the million conversion:
The Decorator pattern lets you attach new behaviors to objects by placing them inside wrapper objects that contain the behaviors. If you're somewhat familiar with React and higher order components (HOC) this kind of approach probably rings a bell for you.
Technically, components in React functions, not objects. But if we think about how React Context or Memo we can see that we're passing a component as a child to this HOC, and thanks to that this child component is able to access certain features.
In this example we can see that the ContextProvider component is receiving children as props:
Then we wrap the whole application around it:
And later on, using the useContext
hook I can access the state defined in the Context from any of the components in my app.
Again, this might not be the exact implementation the book authors had in mind when they wrote about this pattern, but I believe the idea is the same. Place an object within another so it can access certain features. ;)
The Facade pattern provides a simplified interface to a library, a framework, or any other complex set of classes.
Well...we can probably come out with lots of examples for this, right? I mean, React itself or any of the gazillion libraries out there used for pretty much anything related to software development. Specially when we think about declarative programming, it's all about providing abstractions that hide away complexity from the eyes of the developer.
A simple example could be JavaScript's map
, sort
, reduce
and filter
functions, which all work like good 'ol for
loops beneath the hood.
Another example could be any of the libraries used for UI development nowadays, like MUI. As we can see in the following example, these libraries offer us components that bring built-in features and functionalities that help us build code faster and easier.
But all this when compiled turns into simple HTML elements, which are the only thing browsers understand. These components are only abstractions that are here to make our lives easier.
The Proxy pattern provides a substitute or placeholder for another object. The idea is to control access to the original object, performing some kind of action before or after the request gets to the actual original object.
Again, if you're familiar with ExpressJS this probably rings a bell for you. Express is a framework used to develop NodeJS APIs, and one of the features it has is the use of Middlewares. Middlewares are nothing more than pieces of code we can make execute before, in the middle, or after any request reaches our endpoints.
Let's see this in an example. Here I have a function that validates an authentication token. Don't pay much attention to how it does that. Just know that it receives the token as parameter, and once it's done it calls the next()
function.
This function is a middleware, and we can use it in any endpoint of our API in the following way. We just place the middleware after the endpoint address and before declaration of the endpoint function:
In this way, if no token or a wrong token is provided, the middleware will return the corresponding error response. If a valid token is provided, the middleware will call the next()
function and the endpoint function will get executed next.
We could've just written the same code within the endpoint itself and validated the token in there, without worrying about middlewares or anything. But the thing is now we have an abstraction we can reuse in many different endpoints. 😉
Again, this might not have been the precise idea the authors had in mind, but I believe it's a valid example. We're controlling an object's access so we can perform actions at a particular moment.
Behavioral patterns control communication and the assignment of responsibilities between different objects.
The Chain of Responsibility passes requests along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain.
For this pattern we could use the same exact example as before, as middlewares in Express are somehow handlers that either process a request or pass it to the next handler.
If you'd like another example, think about any system in which you have certain information to process along many steps. At each step a different entity is in charge of performing an action, and the information only gets passed to another entity if a certain condition is met.
A typical front-end app that consumes an API could work as an example:
We have a function responsible for rendering a UI component.
Once rendered, a another function makes a request to an API endpoint.
If the endpoint response is as expected, the information is passed to another function that sorts the data in a given way and stores it in a variable.
Once that variable stores the needed information, another function is responsible of rendering it in the UI.
We can see how here we have many different entities that collaborate to execute a certain task. Each of them is responsible for a single "step" of that task, which helps with code modularity and separation of concerns.👌👌
The iterator is used to traverse elements of a collection. This might sound trivial in programming languages used nowadays, but this wasn't always the case.
Anyway, any of the JavaScript built in functions we have at our disposal to iterate over data structures (for
, forEach
, for...of
, for...in
, map
, reduce
, filter
, and so on) are examples of the iterator pattern.
Same as any traversing algorithm we code to iterate through more complex data structures like trees or graphs.
The observer pattern lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing. Basically, it's like having an event listener on a given object, and when that object performs the action we're listening for, we do something.
React's useEffect hook might be a good example here. What useEffect does is execute a given function at the moment we declare.
The hook is divided in two main parts, the executable function and an array of dependencies. If the array is empty, like in the following example, the function gets executed each time the component is rendered.
If we declare any variables within the dependency array, the function will execute only when those variables change.
Even plain old JavaScript event listeners can be thought of as observers. Also, reactive programming and libraries like RxJS, which are used to handle asynchronous information and events along systems, are good examples of this pattern.
If you'd like to know more about this topic, I recommend this great Fireship video and this awesome website where you can find very detailed explanations with illustrations to help you understand each pattern.
As always, I hope you enjoyed the article and learned something new. If you want, you can also follow me on LinkedIn or Twitter.
Cheers and see you in the next one! ✌️
I'm a full stack dev (javascript | typescript | react | react native | node) and computer science student. Here I write about the things I learn along my path to becoming the best developer I can be.
If you read this far, tweet to the author to show them you care. Tweet a thanks
Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started
A facade...