Skip to main content

JavaScript Design Patterns

· 9 min read
Bruno Carneiro
Fundador da @TautornTech

JavaScript Design Patterns

Hey everyone, what's up?

Today I'm going to share some Design Patterns with JavaScript. I'll bring examples of the most common ones and the ones I enjoy using the most in my day-to-day work. I'll try to present clearer cases that can make you think about when a particular Design Pattern will apply to your solution.

This article is not a "recipe book" for when to use what, but rather what each pattern is for and how it can help you. I want to make you think about it rather than draw a straight line...

So let's get going...

But before we start the examples, whatafuck is Design Patterns in Software Engineering?

Design Patterns are nothing more than a way to describe how to solve a problem. It's the creation of a writing model/standard. Since we, as developers, work daily to solve a given problem — which very often has similar solutions and implementations — nothing is better than creating a standard with best practices to formalize how a particular problem was solved, thereby creating a convention for those writing/using that pattern.

This makes it much clearer what is being done and solved.

In short, a Design Pattern aims to facilitate the reuse and design of a solution.

Think of a factory that produces vehicles. When that factory is "invoked" it returns a brand new vehicle with all its attributes and characteristics. When the factory is executed I (who instantiated the factory, so to speak) expect the return of a vehicle (object) and not a function that sums A + B. Since it's a vehicle factory I know it will return a vehicle, just it. Anything different from that is wrong :P

Patterns can be categorized into three main groups:

Creational Design Patterns

These patterns are mechanisms for object creation. A creational pattern basically solves problems by controlling the process of creating an object.

Some of them:

  • Constructor Pattern;
  • Factory Pattern;
  • Prototype Pattern;
  • Singleton Pattern.

Structural Design Patterns

These patterns are concerned with the structure of classes and objects. They help structure or restructure one or more parts without affecting the entire system — in other words, they help achieve new functionality without changing existing functionality. This avoids problems, side effects and incorrect refactoring. Some of them:

  • Adapter Pattern;
  • Composite Pattern;
  • Decorator Pattern;
  • Facade Pattern;
  • Flyweight Pattern;
  • Proxy Pattern.

Behavioral Design Patterns

These patterns are concerned with improving communication between different objects. Some of them:

  • Chain of Responsibility Pattern;
  • Command Pattern;
  • Iterator Pattern;
  • Mediator Pattern;
  • Observer Pattern;
  • State Pattern;
  • Strategy Pattern;
  • Template Pattern.

Now I'm going to demonstrate some usage examples for our everyday life as solution (and bug) creators (I include myself here hahahah).

Hmm... just one more thing: the examples below can be written with either a Class or function/const scope. Of course the syntax changes... I wrote them using function because I believe it makes reading simpler. But it's up to you if you want to reproduce them another way.

Constructor

Well, this pattern I believe is one of, if not the simplest. Constructor is a method used to initialize a new object that is allocated in memory.

/* With function */
function Hero(name, power, color) {
this.name = name;
this.power = power;
this.color = color;

return {
info: () => `Hello my friend, my name is ${this.name}, my costume color is ${this.color} and my super power is ${this.power}`
};
}

const Comics = new Hero("Big D", "Fireball", "purple")
console.log(Comics.info())
// Hello my friend, my name is Big D, my costume color is purple and my super power is Fireball

// Important: Since I'm in a function and want to return private methods I need to put them inside an object and do the `return`.

/* With Class */
class Hero {
constructor(name, power, color) {
this.name = name;
this.power = power;
this.color = color;
}

info() {
return `Hello my friend, my name is ${this.name}, my costume color is ${this.color} and my super power is ${this.power}`
}
}

const Comics = new Hero("Big D", "Fireball", "purple")
console.log(Comics.info())
// Hello my friend, my name is Big D, my costume color is purple and my super power is Fireball

Sandbox

Both cases are equivalent, but in one I used class and in the other function. In JavaScript it's possible to write in both ways, but of course each has its own particularities.

Throughout this article I used functions more out of preference. But nothing prevents you from converting to a Class. The final result needs to be the same. It's the same bicycle, just a different color...

Factory

Think of the factory...

Well, going back to the vehicle factory spoiler, here I expect a method that returns a given "object".

function Tesla() {
const motor = "eletric"
const color = "black"

function getInfo() {
return `I'm a Tesla car with ${motor} motor and ${color}`
}

return {
getInfo
}
}

function GM() {
const motor = "combustion"
const color = "red"

function getInfo() {
return `I'm a GM car with ${motor} motor and ${color}`
}

return {
getInfo
}
}


function Factory() {

function getModel(model) {
const models = Object.freeze({
GM,
Tesla,
})

return models[model]
}

this.createVhicle = (model) => {

try {
return getModel(model)()
} catch (e) {
console.error(e)
}
}
}

const factory = new Factory()
const newCarA = factory.createVhicle("Tesla")
const newCarB = factory.createVhicle("GM")
console.log(newCarA.getInfo())
//I'm a Tesla car with eletric motor and black
console.log(newCarB.getInfo())
//I'm a GM car with combustion motor and red

Sandbox

In the example above I created a car factory. Where I have the Tesla and GM classes, which are car "models".

That's where the logic of Factory comes in — it's a factory of other classes, where I can instantiate a new factory const factory = new Factory() and create a vehicle const newCarA = factory.createVhicle("Tesla"). With that we get all the attributes of each vehicle model without needing to control the logic of creating them, passing all control to Factory.

The idea here is to centralize the logic in the Factory and have it control what to create and why. This allows you to only care about the object you need to use and leave the rest to the factory.

Ah... Object.freeze is just because I don't want the object to be changed — it freezes the object and prevents any modifications to it. It's not a requirement of the Factory pattern.

Singleton

The Singleton pattern limits the number of instances of a class, creating a single instance called, you guessed it, Singleton \o/

This pattern can be useful when the system needs control of some rule — for example — to be handled at a single point. This avoids side effects and method overloading. One example of this is a database connection: in most cases you should only have one that connects, updates, saves, and destroys all connections to the database. This pattern also reduces the need for global variables, which can cause a major headache with side effects.

Example:

function Clock() {
this.timer = new Date();

function WhatTimeIsIt() {
return this.timer;
}

return {
WhatTimeIsIt
};
}

var instance;

const Singleton = (function () {
function createInstance() {
return new Clock();
}

function getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}

return {
getInstance
}
})()

function init() {
const instance1 = Singleton.getInstance()
const instance2 = Singleton.getInstance()
const instance3 = new Clock()

console.info(`Same instance1 === instance2 ? ${instance1 === instance2}`)
// true
console.info(`Same instance1 === instance3 ? ${instance1 === instance3}`)
// false
}

init()

Sandbox

In the example above I created a Clock class, a variable to hold the instances var instance, and the Singleton class.

But first, we need some additional information:

For this class to be considered a Singleton it needs to have only one instance (var instance;), and to be executed with an Immediately Invoked Function Expression (IIFE), (function () { and })()

All those requirements are met.

Back to the explanation...

Notice that when comparing variables instance1 and instance2 they are equal, but when creating instance3 from Clock it is different from instance1, because generating Clock allocates a new instance in memory — unlike when called through our Singleton class which performs that validation.

function getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}

Immediately Invoked Function Expressions (IIFE)

Many people believe this is not a pattern but rather something native to JavaScript, due to the JS scope allowing an anonymous function to be created and immediately executed. But this is a pattern widely used by other patterns, as we saw in Singleton.

IIFE example:

(function(a, b) {
const sum = a + b
console.log(sum)
})(123, 44)

Notice it's the receipt of an anonymous function (has no name to be re-executed, it's created only in that scope) and its execution (function(){ })(). You can also pass arguments (123, 44) to be executed inside the function (function(a, b) {

It is also widely used to simulate static variables

let autoIncrement = (function () {
let number = 0
return function () {
number++
return number
}
})()
console.log(autoIncrement())
// 1
console.log(autoIncrement())
// 2

Sandbox

Every time autoIncrement is called it will increment by one from its previous value. This is possible thanks to the use of closure on a private variable. We are basically returning a new function that will be assigned to the autoIncrement variable, and with the JS scope mechanism the function will always have access to the number variable.

It's also possible to simulate a private variable in JS, as follows:

let autoIncrement = (function() {
let number = 0

return {
incr() {
number++
},
get number() {
return number
}
}
})()

autoIncrement.incr()
console.log(autoIncrement.number) // 1
autoIncrement.incr()
console.log(autoIncrement.number) // 2
autoIncrement.number = 5 // TypeError: Cannot set property number of #<Object> which has only a getter

Sandbox

This way I declare that number is private and that I can only receive its value through reading the number variable and increment it through the incr method.

Conclusion

Well everyone, those are some software engineering development patterns. I only included some of the more common examples but I'll keep adding more over time. I believe you were able to see how they can help us in our day-to-day work, both in improving our writing and in understanding and solving problems. I hope this article helped you a little in understanding them and maybe even helped improve your code.

If you want to learn more about patterns: Software Design Patterns

That's all for now, see you later ;)

References