DEV Community

Tim Winfred (They/Them)
Tim Winfred (They/Them)

Posted on

Design Patterns In JavaScript

I was recently asked by a developer friend of mine what design patterns I use in my job. The question threw me off a bit because I didn't actually know the answer, even though I know that there are definitely patterns that I've used. Do those patterns have names? And why are the patterns I have been using better than others?

For today's post, I will be writing down my notes and thoughts as I learn about a variety of design patterns and how to implement them in JavaScript. Feel free to join me on my journey in this post, or make fun of my noob-ness as I discover something I feel like I should have learned already. (Better late than never, right?)

FYI: One of my primary resources for this post was this blog post from Lambda Test

What is a design pattern?

According to Wikipedia, a software design pattern is...:

A general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.

The Lambda Test article also pointed out that they "represent time-tested solutions and best practices adopted by object-oriented software developers over time."

2021 JavaScript Design Patterns

JavaScript Design Patterns Graphic - a visual of the above information
Source

According to Lambda Test, not all of the patterns are necessary for use in JavaScript since there are native features that implement them for us, so I will only be discussing a select group of design patterns for this post. Feel free to drop me a comment if you want me to add one that I skipped over.

NOTE: All of the quoted definitions below come from the Software Design Patterns entry on Wikipedia

Constructor / Builder Pattern - Creational Design

Separate the construction of a complex object from its representation, allowing the same construction process to create various representations.

Ok, so I've definitely written JavaScript code using the Constructor design pattern before! I guess I just thought it was a feature of classes in JavaScript; I never knew that it was considered a "design pattern".

class Person {
  constructor(name, age, mother) {
    this.name = name;
    this.age = age;
    this.mother = mother;
  }
}

const tim = new Person('Tim', 31, null);
const tina = new Person('Tina', 57, null);

tim.mother = tina;

console.log(tim);

const grandma = new Person('Sherry', 80, null);

tina.mother = grandma;

console.log(tim);
Enter fullscreen mode Exit fullscreen mode

Factory Pattern - Creational Design

Define an interface for creating a single object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

So, based on the way the pattern is implemented in the Lambda Test post, it seems like the name is perfect. It is literally a pattern where you set up a single function that can take a variety of arguments to return the appropriate object. It's like asking a clothing factory for a shirt, they give you a shirt. Ask for pants, they give you pants. Ask for shoes, you get shoes. And each of those objects has it's own functionality.

function animalFactory() {
  this.createAnimal = function(animalType) {
    let animal;

    switch(animalType) {
      case 'dog':
        animal = new Dog();
        break;
      case 'cat':
        animal = new Cat();
        break;
      case 'horse':
        animal = new Horse();
        break;
      default:
        animal = new Monkey();
        break;
    }

    return animal;
  }
}

const Dog = function() {
  this.makeSound = () => {
    console.log('woof woof!');
  }
}

const Cat = function() {
  this.makeSound = () => {
    console.log('prrr prrr meow!');
  }
}

const Horse = function() {
  this.makeSound = () => {
    console.log('neeeighhh!')
  }
}

const Monkey = function() {
  this.makeSound = () => {
    console.log('ooooh ahh oooh oooh!');
  }
}

const factory = new animalFactory();

const jojo = factory.createAnimal('dog');
jojo.makeSound();

const smokey = factory.createAnimal('cat');
smokey.makeSound();

const princess = factory.createAnimal('horse');
princess.makeSound();

const kong = factory.createAnimal('monkey');
kong.makeSound();
Enter fullscreen mode Exit fullscreen mode

Prototype Pattern - Creational Design

Specify the kinds of objects to create using a prototypical instance, and create new objects from the 'skeleton' of an existing object, thus boosting performance and keeping memory footprints to a minimum.

I haven't played around with prototyping very much, so I'm excited to dig into this more, especially since I know it's an awesome feature of JavaScript. In all honesty, this one confused me a lot at first, until I re-read the section in the Lambda Test article and realized it's all about cloning. Once I played around with the implementation below, I was able to figure it out and really understand it.

const macBook = {
  color: 'silver',
  turnOn() {
    console.log('turning on...');
  },
  turnOff() {
    console.log('turning off...');
  }
}

// Proper prototype cloning
const myComputer = Object.create(macBook, { owner: { value: 'Tim'} });
console.log(myComputer.__proto__ === macBook);

// Not a prototype copy
const newComputer = {...macBook, owner: 'John'};
console.log(newComputer.__proto__ === macBook);

macBook.power = 'USB-C';

// The protoype gets the new value for 'power'
console.log(myComputer.power);
// But the non-prototype doesn't
console.log(newComputer.power);
Enter fullscreen mode Exit fullscreen mode

Singleton Pattern / Strict Pattern - Creational Design

Ensure a class has only one instance, and provide a global point of access to it.

So this one makes sure that only one instance exists. If something attempts to make another, it simply returns a reference to the one that already exists. Easily understood.

const Database = (function () {
  let instance;

  function createDatabaseInstance() {
    return new Object('Database Instance');
  }

  function getDatabaseInstance() {
    if (!instance) {
      instance = createDatabaseInstance();
    }

    return instance;
  }

  return { getDatabaseInstance }
})();

const databaseInstance1 = Database.getDatabaseInstance();
const databaseInstance2 = Database.getDatabaseInstance();

console.log(databaseInstance1 === databaseInstance2);
Enter fullscreen mode Exit fullscreen mode

Adapter Pattern / Wrapper Pattern - Structural Design

Convert the interface of a class into another interface clients expect. An adapter lets classes work together that could not otherwise because of incompatible interfaces. The enterprise integration pattern equivalent is the translator.

Having lived abroad for a year, the idea of an adapter is easy enough to understand. It seems like it could possibly be a useful way of connecting a function from legacy code to a function in a newer part of the codebase?

I found this really awesome YouTube video that perfectly explains a use case for the Adapter pattern and was able to use that to create the below implementation to combine two different NPM libraries into one utility. The code I wrote isn't easily copy-pasted into this post, so feel free to check out the code on my Github.

I can definitely imagine A LOT of use cases for this design pattern and actually look forward to using it to help make my code more easily maintainable in the future! Probably my favorite design pattern so far =)

Composite Pattern - Structural Design

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Hey, it's just nodes and components! I definitely know about this design pattern: One parent, multiple children. And those children can have multiple children. It's the one-to-many pattern used in JSON and HTML trees. And now I know the official name of the design pattern for it =)

const Node = function(name) {
  this.children = [];
  this.name = name;
};

Node.prototype = {
  add: function(child) {
    this.children.push(child);
    return this;
  }
}

// recursive console log of what's inside of the tree
const log = (root) => {
  if (!root) return;

  console.log('');
  console.log(`---Node: ${root.name}---`);
  console.log(root);

  root.children.forEach(child => {
    if(child?.children.length) {
      log(child);
    }
  });
}

const init = () => {
  const tree = new Node('root');
  const [left, right] = [new Node("left"), new Node("right")];
  const [leftleft, leftright] = [new Node("leftleft"), new Node("leftright")];
  const [rightleft, rightright] = [new Node("rightleft"), new Node("rightright")];

  tree.add(left).add(right);
  left.add(leftleft).add(leftright);
  right.add(rightleft).add(rightright);

  log(tree);
}

init();
Enter fullscreen mode Exit fullscreen mode

Module Pattern - Structural Design

Group several related elements, such as classes, singletons, methods, globally used, into a single conceptual entity.

This design pattern reminds me of when I learned Java: Getting to give your function private functions and variables, while only exposing the functions and variables that are necessary. I have definitely used this design pattern multiple times in my career. Good to know it has a name...

const userApi = () => {
  // private variables
  const users = [];

  // private function
  const addUser = (name) => {
    users.push(name);

    return users[users.length -1];
  }

  // private function
  const getAllUsers = () => {
    return users;
  }

  // private function
  const deleteUser = (name) => {
    const userIndex = users.indexOf(name);

    if (userIndex < 0) {
      throw new Error('User not found');
    }

    users.splice(userIndex, 1);
  }

  // private function
  const updateUser = (name, newName) => {
    const userIndex = users.indexOf(name);

    if (userIndex < 0) {
      throw new Error('User not found');
    }

    users[userIndex] = newName;

    return users[userIndex];
  }

  return {
    // public functions
    add: addUser,
    get: getAllUsers,
    del: deleteUser,
    put: updateUser
  }
}

const api = userApi();

api.add('Tim');
api.add('Hina');
api.add('Yasmeen');
api.add('Neeloo');

console.log(api.get());

api.del('Yasmeen');

console.log(api.get());

api.put('Tim', 'Tim Winfred');

console.log(api.get());
Enter fullscreen mode Exit fullscreen mode

Decorator Pattern - Structural Design

Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.

Ok, this one is fun and easy to understand. I can set up a function to give an object common functionality and traits, but then I can "decorate" each individual instance of that object with it's own functionality and traits. I can definitely see myself using this at some point in the future.

const Animal = function(type) {
  this.type = type || 'dog';
}

const dog = new Animal('dog');
const cat = new Animal('cat');

dog.bark = function() {
  console.log('woof woof!');
  return this;
}

cat.meow = function() {
  console.log('meow meooooooow!');
  return this;
}

dog.bark();
cat.meow();
Enter fullscreen mode Exit fullscreen mode

Facade Pattern - Structural Design

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

I'm actually fairly familiar with the use of a facade. I've been leveraging a pre-built facade on one of the projects I am currently working on at work. (It is even imported with the name facade, but I had no idea that this was a "design pattern".)

I found this video on JavaScript facade design that was really helpful. (The guy presenting in that video has a LOT of really helpful videos.)

import axios from 'axios';

function getUsers() {
  return facade.get('https://jsonplaceholder.typicode.com/users');
}

function getUserById(id) {
  return facade.get('https://jsonplaceholder.typicode.com/users', { id });
}

const facade = {
  get: function(url, params) {
    return axios({
      url,
      params,
      method: 'GET'
    }).then(res => res.data);
  }
};

async function getData() {
  try {
    console.time('getUsers took');
    const users = await getUsers();
    console.timeEnd('getUsers took');
    console.log(`There are ${users.length} users`);

    console.time('getUserById took');
    const user1 = await getUserById(1);
    console.timeEnd('getUserById took');
    console.log(user1);
  } catch (error) {
    console.log(error);
  }
}

getData();
Enter fullscreen mode Exit fullscreen mode

Proxy Pattern - Structural Design

Provide a surrogate or placeholder for another object to control access to it.

As a Magic: The Gathering player, this one makes sense in theory: Don't use the expensive card when you can use a proxy card instead.

I found this highly-rated YouTube video about the Proxy Design Pattern and how it can be used in JavaScript to create a cache to save time and reduce the number of times you have to hit an external API.

// Mock External API
function CryptoCurrencyAPI() {
  this.getValue = function(coin) {
    console.log(`Calling Crypto API to get ${coin} price...`);

    switch(coin.toLowerCase()) {
      case 'bitcoin':
        return 38000;
      case 'ethereum':
        return 2775;
      case 'dogecoin':
        return 0.39;
    }
  }
}

function CryptoCurrencyAPIProxy() {
  this.api = new CryptoCurrencyAPI();
  this.cache = {};

  this.getValue = function(coin) {
    if(!this.cache[coin]) {
      console.log(`The value of ${coin} isn't stored in cache...`);
      this.cache[coin] = this.api.getValue(coin);
    }

    return this.cache[coin];
  }
}

const proxyAPI = new CryptoCurrencyAPIProxy();

console.log(proxyAPI.getValue('Bitcoin'));
console.log(proxyAPI.getValue('Bitcoin'));
console.log(proxyAPI.getValue('Ethereum'));
console.log(proxyAPI.getValue('Ethereum'));
console.log(proxyAPI.getValue('Dogecoin'));
console.log(proxyAPI.getValue('Dogecoin'));
Enter fullscreen mode Exit fullscreen mode

Chain of Responsibility Pattern - Behavioral Design

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

This is another one of those patterns that I definitely understand in theory and can connect to a lot of real world scenarios, but I can't immediately think of an implementation for coding aside from the sample given in the Lambda Test article.

After playing around with it a bit, I actually do like this design pattern and enjoy the ease of method chaining. What do you think of what I created? I'm not quite sure it fulfills the "object" definition from Wikipedia...

const ATM = function() {
  this.withdrawl = function(amount) {
    console.log(`Requesting to withdrawl $${amount.toFixed(2)}`);

    if (amount % 1 !== 0) {
      console.log('Sorry, this ATM can\'t dispense coins. Please request another amount.');
      return;
    }

    const dispenseOutput = {};

    // chain or responsibility function
    function get(bill) {
      dispenseOutput[bill] = Math.floor(amount / bill);
      amount -= dispenseOutput[bill] * bill;

      return { get };
    }

    get(100).get(50).get(20).get(10).get(5).get(1);

    this.dispense(dispenseOutput);
  };

  this.dispense = function(bills) {
    console.log('--- Dispensing cash ---')
    Object.entries(bills).forEach(([key, value]) => {
      console.log(`- Dispensing ${value} $${key} bills`);
    });
  }
};

const myATM = new ATM();

myATM.withdrawl(126.10);
myATM.withdrawl(1287);
myATM.withdrawl(879);
Enter fullscreen mode Exit fullscreen mode

Command Pattern - Behavioral Design

Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests, and the queuing or logging of requests. It also allows for the support of undoable operations.

This one seems pretty easy, and I really like the separation of the command object and the receiver. Makes for clean code. I actually was trying to create a calculator with React recently, so this is the perfect solution!

const calculationMethods = {
  add: function(x, y) {
    return x + y;
  },
  subtract: function(x, y) {
    return x - y;
  },
  multiply: function(x, y) {
    return x * y;
  },
  divide: function(x, y) {
    return x / y;
  }
};

const calculator = {
  execute: function(method, num1, num2) {
    if (!(method in calculationMethods)) return null;

    return calculationMethods[method](num1, num2);
  }
};

console.log(calculator.execute('add', 1, 2));
console.log(calculator.execute('subtract', 5, 2));
console.log(calculator.execute('multiply', 11, 2));
console.log(calculator.execute('divide', 10, 2));
console.log(calculator.execute('square root', 20));
Enter fullscreen mode Exit fullscreen mode

Observer (Pub/Sub) Pattern - Behavioral Design

Define a one-to-many dependency between objects where a state change in one object results in all its dependents being notified and updated automatically.

My immediate thought is that this makes me think of "observables" in JavaScript, which I haven't played around with too much (at least not in a while). The pub/sub idea of this pattern also makes me think of websockets and Redux.

In my below implementation, I used the example from this freeCodeCamp video that has great explaintion of the observer pattern. I switch things up a bit and implemented some method chaining:

function Subject() {
  this.observers = [];
}

Subject.prototype = {
  subscribe: function(observer) {
    this.observers.push(observer);

    return this;
  },

  unsubscribe: function(observer) {
    const indexOfObserver = this.observers.indexOf(observer);
    if (indexOfObserver > -1) {
      this.observers.splice(indexOfObserver, 1);
    }

    return this;
  },

  notifyObserver: function(observer) {
    const indexOfObserver = this.observers.indexOf(observer);
    if (indexOfObserver > -1) {
      this.observers[indexOfObserver].notify();
    }

    return this;
  },

  notifyAllObservers: function() {
    this.observers.forEach(observer => {
      observer.notify();
    });

    return this;
  }
}

function Observer(name) {
  this.name = name;
}

Observer.prototype = {
  notify: function() {
    console.log(`Observer ${this.name} has been notified`);
  }
};

const subject = new Subject();

const observer1 = new Observer('user001');
const observer2 = new Observer('user002');
const observer3 = new Observer('user003');
const observer4 = new Observer('user004');
const observer5 = new Observer('user005');

subject.subscribe(observer1).subscribe(observer2).subscribe(observer3).subscribe(observer4).subscribe(observer5);
subject.notifyObserver(observer4);
subject.unsubscribe(observer4);
subject.notifyAllObservers();
Enter fullscreen mode Exit fullscreen mode

Template Method Pattern - Behavioral Design

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

Ok, so I definitely don't understand this based on that description haha. It sounds like it's extending classes, and giving the new sub-class new methods, but I definitely have to see if I can find a better description of this one...

Unlike the definition of this pattern, this explanation of the Template Method Pattern from Microsoft helped me understand what is happening (although they're not using JavaScript), as did this blog post.


class HouseTemplate {
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  buildHouse() {
    this.buildFoundation();
    this.buildPillars();
    this.buildWalls();
    this.buildWindows();
    console.log(`${ this.name } has been built successfully at ${ this.address }`);
  }

  buildFoundation() {
    console.log('Building foundation...');
  }

  buildPillars() {
    throw new Error('You have to build your own pillars');
  }

  buildWalls() {
    throw new Error('You have to build your own walls');
  }

  buildWindows() {
    console.log('Building windows');
  }
}

class WoodenHouse extends HouseTemplate {
  constructor(name, address) {
    super(name, address);
  }

  buildPillars() {
    console.log('Building pillars for a wooden house');
  }

  buildWalls() {
    console.log('Building walls for a wooden house');
  }
}

class BrickHouse extends HouseTemplate {
  constructor(name, address) {
    super(name, address);
  }

  buildPillars() {
    console.log('Building pillars for a brick house');
  }

  buildWalls() {
    console.log('Building walls for a brick house');
  }
}

const woodenHouse = new WoodenHouse('Wooden house', '123 Maple Road');
const brickHouse = new BrickHouse('Brick house', '456 Stone Lane');

woodenHouse.buildHouse();
brickHouse.buildHouse();
Enter fullscreen mode Exit fullscreen mode

Strategy Pattern - Behavioral Design

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Alright, so this is the last one! And, thankfully, it's super simple to understand thanks to the Lambda Test Article =) I appreciate any method that allows for DRY coding and can definitely imagine a variety of implementations for this in the future.

function Regal() {
  this.getTicketPrice = function(quantity) {
    return quantity * 11.99;
  }
}

function AMC() {
  this.getTicketPrice = function(quantity) {
    return quantity * 10.99;
  }
}

function Cinemark() {
  this.getTicketPrice = function(quantity) {
    return quantity * 9.99;
  }
}

function TicketPrice() {
  this.theaterChain;

  this.setTheaterChain = function(chain) {
    this.theaterChain = chain;
  }

  this.calculate = function(quantity) {
    return this.theaterChain.getTicketPrice(quantity);
  }
}

const regal = new Regal();
const amc = new AMC();
const cinemark = new Cinemark();

const ticketPrice = new TicketPrice();
ticketPrice.setTheaterChain(regal);
console.log(ticketPrice.calculate(2));

ticketPrice.setTheaterChain(amc);
console.log(ticketPrice.calculate(3));

ticketPrice.setTheaterChain(cinemark);
console.log(ticketPrice.calculate(4));
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

And that's all folk (for now)!

I honestly had so much of fun digging into all of these design patterns, and am so happy I did. I skipped over a few patterns that have ES6 implementations in JavaScript, but overall I definitely learned a lot.

If you read this far, I hope my journey and my code has been helpful to you. See ya soon!

Visit my Github repo to get all of the code above in one place.

Discussion (10)

Collapse
arvindpdmn profile image
Arvind Padmanabhan

I would have thought ES6 classes is a better way to implement these design patterns. Only your example on HouseTemplate does this. Anyway, inspiring post. We'll create a similar article in detail on Devopedia. We do have an article on CSS design patterns: devopedia.org/css-design-patterns

Collapse
dewaldels profile image
Dewald Els

I personally prefer the functional nature of JavaScript. I do see value in using classes from time to time, but I really love the way functions work in JS. 😅

Collapse
lilylaw profile image
Lily

Thanks for writing this. I too have used many of these without realising. I believe recognising these can only make my code clearer and cleaner.
As for the Chain of Responsibility Pattern the example that springs to my mind is middleware in express.

Collapse
mefaba profile image
AW A RE

Throwing design patterns to reader(us) without any given context does not make any sense to me. I rather prefer one design pattern explained with in a context. By context, I mean, when you use it in an actualy scenario. And I don't know why but even book about design pattterns does not give that insight.

Collapse
ishakmohmed profile image
Mohmed Ishak

True software engineer check.

Collapse
twinfred profile image
Tim Winfred (They/Them) Author

Haha, thanks! It definitely feels like a step up =)

Collapse
kleguizamon profile image
Kevin Leguizamon

Thanks for this article, it’s great!!

Collapse
twinfred profile image
Tim Winfred (They/Them) Author

Glad you enjoyed! Thanks for checking it out =)

Collapse
tmsravan profile image
TMsravan

Thanks for this article:)

For your support for the community.

Collapse
eduardorangell profile image
Eduardo Rangell

Thanks for share this important knowledge !!