Telerik blogs
JavaScriptT2 Light_1200x303

Design patterns are documented solutions to commonly occurring problems in software engineering. Engineers don’t have to bang their heads on the problems that someone else has already solved.

One fine day, I decided to resolve this bug that was pending for quite a long time – Multiple Instances of the IndexedDB reference are getting created. If you’re wondering what IndexedDB is, it’s a client-side persistent key-value data store, commonly used in Progressive Web Applications for faster data access.

With the IndexedDB context in place, let’s get back to the bug – I somehow have to prevent the creation of multiple instances of IndexedDB. It should be initialized only once, and every other attempt to re-initialize the IndexedDB instance should not succeed and should return the existing reference.

While I was whispering this and looking patiently at my editor to get some clues out of the existing code, one of my colleagues gushed, “Just use a singleton!” Huh, what’s a singleton? She explained by saying, create one global variable to store the IndexedDB reference and on every other request just check if this global variable is already initialized. If the variable is already initialized, simply return it; otherwise create its instance and store it in the global variable before returning.

I contended, “Got it! But why is this called a singleton and who named it?” She explained further, “Singleton is one of the design patterns in object-oriented paradigm and it simply means that a class can only be instantiated once. This is a common pattern and can be reused for solving problems of this nature.” The term Design Patterns got me curious and I began searching about it on the internet!

What are Design Patterns?

Design patterns are documented solutions to commonly occurring problems in software engineering. Engineers don’t have to bang their heads on the problems that someone else has already solved.

While writing code, people observed that a lot of time is spent thinking over solutions to common problems. There is no single way of solving these problems. Smart engineers started finding patterns in these common problems and they documented these problems and efficient ways of solving them. The book Design Patterns: Elements of Reusable Object-Oriented Software, also called GoF book (Gang of Four as it is written by four writers), explains 23 classic software design patterns and is a treasure trove for every aspiring software engineer out there!

“Design patterns” is common in most engineering conversations. People don’t have to spend time explaining the same problem again and again — there’s a term for each of these problems! The book mainly explains the design patterns in the context of object-oriented languages like C++ and Java, and all of its solutions are in C++.

JavaScript guy? Don’t you worry! The problem and intent of most of these patterns are applicable in JavaScript too. And the good news is we have a concrete book to follow and learn all of these patterns in JavaScript! Addy Osmani has got us covered. He has written the book Learning JavaScript Design Patterns, and it’s the most popular book for becoming an expert in using design patterns in JavaScript. I highly recommend reading this amazing book. But If you’re looking for a quick guide to the most commonly used design patterns in JavaScript, this article is the right place to get you started!

Now that you know design patterns are common in most engineering conversations, it makes sense to know these terms to speed up product development cycles. Let’s get started!

Categories of Design Patterns

Design patterns are divided into many categories, but the most common are Creational, Structural and Behavioral. Here’s a quick overview of these three categories:

Design patterns image 1 

Hang tight! We’ll be learning some of the design patterns in each of the categories. Let’s start by understanding more about creational design patterns.

Creational Design Patterns

Creational design patterns deal with various object creation mechanisms. These mechanisms make it easy to reuse the existing code and help in writing scalable and flexible code.

Creational design patterns abstract the complex logic of creating objects from the client and provide a cleaner interface to solve particular problems. Some of the engineering problems may require you to only have a single instance of a class. Look no further and use Singleton! The prototype design pattern lets you create clones of objects, while the builder pattern lets you create complex objects step by step. Let’s start with the easiest pattern, the constructor pattern.

Constructor Design Pattern

The constructor method should be a no-brainer if you come from a classic object-oriented background. The constructor method gets called whenever we create an object of a class. Here the class represents an entity, something like Car, Person, etc. A class contains member properties and methods and each of its objects has its own copy of these properties but share common method definitions.

Here’s a simple Pokemon class written in TypeScript:

class Pokemon {
    name: string
    baseExperience: number
    abilities: string[]

    constructor(name: string, baseExperience: number, abilities: string[]) {
        this.name = name
        this.baseExperience = generation
        this.abilities = [...abilities]
    }

    addAbility(ability: string) {
        /* Method to add new abilities */
    }    
}

The Pokemon class contains the member properties name, baseExperience and abilities and a method as addAbility. The constructor method is a special method that gets called when we instantiate the class using the new operator. The constructor method does the work of assigning values to the instance variables of the class to create a new object.

let bulbasaurObj = new Pokemon("bulbasaur", 64, ["chlorophyll"])

Once the above statement gets executed, the properties name, baseExperience and abilities and addAbility method are defined on the bulbasaurObj object. The client creating the Pokemon object doesn’t know how this happens behind the scenes. The constructor abstracts out these details of attaching the member properties and methods on the object.

We usually deal with objects in JavaScript. Let’s see how objects are created using object constructors in JavaScript:

/* Three ways of creating objects in JavaScript */

let obj = {}
/* ----------- */
let obj = new Object()
/* ----------- */
let obj = Object.create()

The above statements look simple! But the main guy, constructor, does all the work of wrapping the object obj with all the properties and methods available on the parent Object. Here’s what gets done when you execute any of the above statements:

Design patterns image 3

The constructor defined for Object does the work of attaching the methods on the __proto__ property of the object obj.

We can also pass in another prototype as an argument as:

let obj = Object.create(Pokemon.prototype)

Design patterns image 4

Notice how both the methods constructor and addAbility of the Pokemon class are attached on the __proto__ property of the object obj. The __proto__ to the __proto__ property contains the base Object class methods.

We have been talking about prototypes here, but what exactly are those? The prototype is also one of the creational design patterns. Let’s check that out!

Prototype Design Pattern

The prototype design pattern lets us create clones of the existing objects. This is similar to the prototypal inheritance in JavaScript. All of the properties and methods of an object can be made available on any other object by leveraging the power of the __proto__ property. Here’s a quick way to do it using the ES6 Object.create method:

let obj = Object.create(Pokemon.prototype)

We can also achieve the prototypal inheritance using the classic functional objects as:

let shapePrototype = {
    width: 10,
    height: 10,

    draw: function (shape) {

    }
}

function Rectangle () {}

/* The prototype of Rectangle is shapePrototype, which means Rectangle should be cloned as shapePrototype */
Rectangle.prototype = shapePrototype

let rectObj = new Rectangle()

/* draw method is present on the rectObj as shapePrototype is attached to it __proto__ property */
rectObj.draw('rectangle')

This is how we use prototypal inheritance in practice! The prototype design pattern is generally used to implement inheritance in JavaScript. It is used to add the properties of the parent to the child objects. Please note these inherited properties are present on the __proto__ key.

Side note: You can read more on how the scope and the scope chain operates on prototypes here.

Singleton Design Pattern

Singleton pattern is what got us excited to dive deep into design patterns! As mentioned earlier, the singleton design pattern lets us create no more than a single instance of a class. It is commonly used for creating database connections, setting up a logger for an application. The configuration-related stuff should execute only once and should be reused until the application is live.

Here’s a simple example of a singleton design pattern:

let dbInstance = null

function getDBConn () {
    if (!dbInstance) {
        dbInstance = new DB() // Creating an instance of DB class and storing it in the global variable dbInstance
    }
    return dbInstance
}

function useDBConn () {
    let dbObj = getDBConn()
    /* --- */
}

function f1 () {
    let dbObj = getDBConn()
    /* --- */
}

function f2 () {
    let dbObj = getDBConn()
    /* --- */
}

The dbInstance variable is scoped globally and the functions useDBConn, f1 and f2 need dbInstance for processing something. If not for the if check in the getDBConn function, each of the dbObj would point to different database objects. The getDBConn instantiates the DB class only if the dbInstance variable is not defined.

We are lazily evaluating the value of the dbInstance variable. This is also called Lazy Initialization. The singleton design pattern is tightly coupled to creating only one instance of a class, but we may require more than one object of a class in some use cases. It is possible that the application needs to create two database connections. The above implementation fails in this case. But we can tweak the above implementation to create only a particular number of instances. Please note: this workaround will no longer be called singleton then!

We have created the dbInstance variable in the global scope, but it is not a good idea to pollute the global scope. Let’s see if we can do anything better here!

let singletonWrapper = (function () {
    let instance

    function init () {
        let randomNumber = Math.random()

        return {
            getRandomNumber: function () {
                return randomNumber
            }
        }
    }

    /* The IIFE returns an object with getInstance as one of the methods and abstracts all other details */
    return {
        getInstance: function () {
            if (!instance) {
                instance = init()
            }
            return instance
        }
    }
})()

This might look scary, but nothing much is going on here! There’s just one IIFE; its return value is stored in a variable called singletonWrapper. The IIFE return an object that has a function getInstance. The variable instance is a singleton and should be initialized only once. The init method returns an object with getRandomNumber.

We will now create two instances using the singletonWrapper and, if everything is correct, both of these instances should have the same random number. Let’s get to it!

Design patterns image 5

Please note: The random number for both the objects a and b is the same. That’s the power of singleton!

Structural Design Patterns

Real-world applications are not built using objects of just one type. We create multiple types of objects and fit them together to construct something meaningful. The structural design patterns let us compose different objects in a large structure. These patterns help in building relationships between different objects while making the structure flexible and efficient.

Here are some of the structural design patterns:

Adapter Design Pattern

It allows two objects of different shapes (format or structure) to work together through a common interface. Let’s say you have to build a charts library and it accepts data in a structured JSON object to render beautiful charts. You have one legacy API that returns the response in XML format. We have to use this response and generate charts, but the charts library accepts a JSON object. We will write a function to convert this XML to JSON as required. This very function that lets us connect two incompatible structures is an adapter.

Design patterns image 2

Composite Design Pattern

The composite design pattern is my favorite. We have been using this for a long time without knowing the proper term. Remember the old jQuery days!

$("#element").addClass("blur") // element is an id
$(".element").addClass("blur") // element is a class
$("div").addClass("blur") // native element div
$(".element.isActive").addClass("blur") // trying to access a DOM node that has element as well as isActive class
$(".element h2").addClass("blur") // trying to access the native element h2 which is inside a node with class element

jQuery made it super easy to access elements of any combination and apply different methods on the selected DOM nodes. The method addClass hides the implementation details of accessing elements of different kinds. The composite pattern brings flexibility in an application and makes sure the group of objects behaves in the same way as an individual object.

Decorator Design Pattern

The decorator pattern lets you add new properties to objects by placing these objects inside wrappers. If you have some experience with the react-redux module, you might be familiar with its connect method. The connect method is a higher-order function that takes in a component as an argument, wraps some properties over it, and returns another component that has these additional properties along with the original ones. The connect method decorates the component to accommodate additional properties.

The decorator pattern lets us add properties to a particular object at run-time, and this does not affect any other object of the same class.

Here’s an example that fits well with the decorator pattern:

You’re at Starbucks and you have all the freedom to customize your coffee as you like. There are more than 2000 variations. You can select different sizes, add different syrups, and can have extra shots. You can select soy milk or regular milk.

Each of these permutations and combinations yields a different cup of coffee and comes with its own cost.

One way to put this into code is to have a class for every possible arrangement like CoffeeWithExpresso, CoffeeWithSoyMilk and many more. Each of these classes can extend the main Coffee class. Don’t you think this would be a mess to have a subclass for each of these options?

Ahh! Can we add all of these properties in a single class and get away with it? Most of these properties will never be used for creating objects and this technique opposes the object-oriented paradigm of having separate classes for doing different work.

How do we solve this problem?

We simply create a wrapper on the original object and add different properties as needed only to this object. This makes our class code clean and only lets us decorate a particular object by adding a few more properties.

Facade Design Pattern

This pattern abstracts the underlying complexity and provides a convenient high-level interface. The most popular $ (jQuery) would do just everything for us! No document.createElement and stressing up because of UI issues on different browsers. jQuery provided an easy-to-use library of functions to interact with the DOM nodes. And that’s a facade pattern!

Proxy Design Pattern

The proxy design pattern lets you provide a substitute or proxy for another object. You have a chance to modify the original object before it goes into the actual execution snippet. Here’s an example:

let pokemon = {
    name: "butterfree",
    attack: function () {
        console.log(`${this.name} is all set to attack!`)
    }
}
setTimeout(pokemon.attack, 100) // Prints undefined is all set to attack

Argh! Yes, the value of this is no longer valid in the setTimeout callback function. We’ll have to change this to:

setTimeout(pokemon.attack.bind(pokemon), 100) // Prints butterfree is all set to attack

The bind method does the work! The bind method lets us substitute the original object (window or global object) to the proxy object pokemon. That’s one of the examples of a proxy pattern.

Behavioral Design Patterns

The behavioral design patterns focus on improving communication between different objects in a system. Here’s a quick overview of some of the behavioral design patterns:

Chain of Responsibility Design Pattern

This design pattern lets us build a system where each request passes through a chain of handlers. The handler either processes the request and passes it to another one in the chain, or it simply rejects the request. This pattern is commonly used in systems where sequential checks are required to be performed on the incoming requests.

Let’s consider a simple example of an express server. The incoming requests are intercepted by middleware; the middleware processes the request and passes it onto the next middleware in chain.

Consider an example of an online ordering system. The first middleware in the chain parses the request body and converts it into a valid format. It is then forwarded to the next middleware that checks if the user credentials are valid. The request is then forwarded to the next middleware in the chain and so on! This is termed as a chain of responsibility design pattern.

Please note: any of the middleware can also reject the request and stop propagating it through the chain if the request is deemed invalid.

Iterator Design Pattern

The iterator design pattern lets you traverse elements of a collection (Arrays, LinkedList, Trees, Graphs, etc.) without exposing its underlying implementation.

Iterating in simple data structures like Arrays is no big deal! You loop through the elements and print them in sequential order. But we can implement different traversal algorithms for tree-based data structures. We can have depth-first Inorder, Preorder, Postorder or even some breadth-first algorithm. We might also want to change the traversal algorithm after a few days. These changes should not impact the client who is using your data structure. The iterator should have its own concrete class, and the traversal details should be hidden from the client.

The client should only have access to some traverse method. This makes it flexible to change the traversal algorithms behind the scenes!

Observer Design Pattern

The observer design pattern lets you define subscription mechanisms so that objects can communicate the changes happening in one part of the system to the rest of the world.

The most common example of the observer pattern is the notification system. Applications generally have one notification service. The job of this service is to notify users of any updates that he is subscribed to (or updates relevant to him). When a user confirms his order and makes the payment, we usually show him an alert confirming the order. The payment service publishes a message describing a change in the system. The notification service is interested in listening to updates from the payment service. Once the notification service is made aware of this update, it renders a nice-looking alert to the screen!

The object that wants to share state/updates to the system is called the subject or publisher. The objects that are interested in listening to these updates are called subscribers.

Let’s implement a simple example that uses the observer pattern:

class Subject {
    constructor () {
        this.criticalNumber = 0
        this.observers = []
    }

    addObserver (observer) {
        this.observers.push(observer)
    }

    removeObserver (observer) {
        let index = this.observers.findIndex(o => o === observer)
        if (index !== -1) {
            this.observers.splice(index, 1)
        }
    }

    notify () {
        console.log('Notifying observers about some important information')
        this.observers.forEach (observer => {
            observer.update(this.criticalNumber)
        })
    }

    changeCriticalNumber () {
        /* Changing the critical information */
        this.criticalNumber = 42
        /* Notifying the observers about this change */
        this.notify()
    }
}

class Observer {
    constructor (id) {
        this.id = id
    }

    update (criticalNumber) {
        console.log(`Observer ${this.id} - Received an update from the subject ${criticalNumber}`)
    }
}

We have two created classes here, Subject and Observer. The Subject holds some critical information and a list of observers. Whenever the state of this critical information changes, the Subject notifies about this change to all its observers using the notify method. The Observer class has an update method that gets called on every notification request from the Subject.

let s = new Subject()
let o1 = new Observer(1)
let o2 = new Observer(2)

s.addObserver(o1)
s.addObserver(o2)

/* Changing the critical information */
s.changeCriticalNumber()

Let’s see this in action!

Design patterns image 6

Both the observers o1 and o2 have received an update about the change in the critical information criticalNumber. Sweet! That’s an observer pattern.

There are other behavioral patterns such as Command, Iterator, Mediator, Memento, State, Strategy, Template method and Visitor, but they are not in the scope of this article. I’ll cover them in the next set of articles.

Conclusion

We learned that design patterns play an important role in building applications. The commonly used categories of the design patterns are creational, structural and behavioral. The creational design patterns are focused on object creation mechanisms; structural design patterns help in composing different objects and realizing relationships between them; and behavioral patterns help in building communication patterns between different objects.

Then we learned some of the patterns in these categories, such as constructor, prototype, singleton, decorator, proxy, observer and iterator.


Ankita Masand
About the Author

Ankita Masand

A Software engineer trying to make sense of every line of code she writes, Ankita is a JavaScript enthusiast and adores its weird parts. She’s also an obsessed developer, highly focused on the performance of web applications. When she’s not writing JavaScript, you’ll find her engaged in understanding the intricacies of deep neural networks.

Related Posts

Comments

Comments are disabled in preview mode.