Book Image

JavaScript Design Patterns

By : Hugo Di Francesco
Book Image

JavaScript Design Patterns

By: Hugo Di Francesco

Overview of this book

Unlock the potential of JavaScript design patterns, the foundation for development teams seeking structured and reusable solutions to common software development challenges in this guide to improving code maintainability, scalability, and performance. Discover how these patterns equip businesses with cleaner and more maintainable code, promote team collaboration, reduce errors, and save time and costs. This book provides a comprehensive view of design patterns in modern (ES6+) JavaScript with real-world examples of their deployment in professional settings. You’ll start by learning how to use creational, structural, and behavioral design patterns in idiomatic JavaScript, and then shift focus to the architecture and UI patterns. Here, you’ll learn how to apply patterns for libraries such as React and extend them further to general web frontend and micro frontend approaches. The last section of the book introduces and illustrates sets of performance and security patterns, including messaging and events, asset and JavaScript loading strategies, and asynchronous programming performance patterns. Throughout the book, examples featuring React and Next.js, in addition to JavaScript and Web API examples, will help you choose and implement proven design patterns across diverse web ecosystems, transforming the way you approach development.
Table of Contents (16 chapters)
1
Part 1:Design Patterns
5
Part 2:Architecture and UI Patterns
9
Part 3:Performance and Security Patterns

The factory pattern in JavaScript

In a similar fashion to the discussion about the JavaScript “prototype” versus the prototype creational design pattern, “factory” refers to related but different concepts when it comes to general program design discussions and design patterns.

A “factory,” in the general programming sense, is an object that’s built with the goal of creating other objects. This is hinted at by the name that refers to a facility that processes items from one shape into another (or from one type of item to another). This factory denomination means that the output of a function or method is a new object. In JavaScript, this means that something as simple as a function that returns an object literal is a factory function:

const simpleFactoryFunction = () => ({}); // returns an object, therefore it's a factory.

This definition of a factory is useful, but this section of the chapter is about the factory design pattern, which does fit into this overall “factory” definition.

The factory or factory method design pattern solves a class inheritance problem. A base or superclass is extended (the extended class is a subclass). The base class’s role is to provide orchestration for the methods implemented in the subclasses, as we want the subclasses to control which other objects to populate an instance with.

Implementation

A factory example is as follows. We have a Building base class that implements a generateBuilding() method. For now, it’s going to create a top floor using the makeTopFloor instance method. In the base class (Building), makeTopFloor is implemented, mainly because JavaScript doesn’t provide a way to define abstract methods. The makeTopFloor implementation throws an error because subclasses should override it; makeTopFloor is the “factory method” in this case. It’s how the base class defers the instantiation of objects to the subclasses:

class Building {
  generateBuilding() {
    this.topFloor = this.makeTopFloor();
  }
  makeTopFloor() {
    throw new Error('not implemented, left for subclasses
      to implement');
  }
}

If we wanted to implement a single-story house, we would extend Building and override makeTopFloor; in this instance, topFloor will have level: 1.

class House extends Building {
  makeTopFloor() {
    return {
      level: 1,
    };
  }
}

When we instantiate House, which is a subclass of Building, we have access to the generateBuilding method; when called, it sets topFloor correctly (to { level: 1 }).

const house = new House();
house.generateBuilding();
console.assert(house.topFloor.level === 1, 'topFloor works
  in House');

Now, if we want to create a different type of building that has a very different top floor, we can still extend Building; we simply override makeTopFloor to return a different floor. In the case of a skyscraper, we want the top floor to be very high, so we’ll do the following:

class SkyScraper extends Building {
  makeTopFloor() {
    return {
      level: 125,
    };
  }
}

Having defined our SkyScraper, which is a subclass of Building, we can instantiate it and call generateBuilding. As in the preceding House case, the generateBuilding method will use SkyScraper’s makeTopFloor method to populate the topFloor instance property:

const skyScraper = new SkyScraper();
skyScraper.generateBuilding();
console.assert(skyScraper.topFloor.level > 100, 'topFloor
  works in SkyScraper');

The “factory method” in this case is makeTopFloor. The makeTopFloor method is “not implemented” in the base class, in the sense that it’s implemented in a manner that forces subclasses that wish to use generateBuilding to define a makeTopFloor override.

Note that makeTopFloor in our examples returned object literals, as mentioned earlier in the chapter; this is a feature of JavaScript not available in all object-oriented languages (JavaScript is multi-paradigm). We’ll see different ways to implement the factory pattern later in this section.

Use cases

The benefit of using a factory method is that we can create a wide variety of subclasses without modifying the base class. This is the “open/closed principle” at play – the Building class in our example is “open” to extension (i.e., can be subclassed to infinity for different types of buildings) but “closed” to modification (i.e., we don’t need to make changes in Building for every subclass, only when we want to add new behaviors).

Improvements with modern JavaScript

The key improvement we can make with JavaScript is enabled by its first-class support for functions and the ability to define objects using literals (instead of classes being instantiated).

JavaScript having “first-class functions” means functions are like any other type – they can be passed as parameters, set as variable values, and returned from other functions.

A more idiomatic implementation of this pattern would probably involve a generateBuilding standalone function instead of a Building class. generateBuilding would take makeTopFloor either as a parameter or take an object parameter with a makeTopFloor key. The output of generateBuilding would be an object created using an object literal, which takes the output of makeTopFloor() and sets it as the value to a topFloor key:

function generateBuilding({ makeTopFloor }) {
  return {
    topFloor: makeTopFloor(),
  };
}

In order to create our house and skyscraper, we would call generateBuilding with the relevant makeTopFloor functions. In the case of the house, we want a top floor that is on level 1; in the case of the skyscraper, we want a top floor on level 125.

const house = generateBuilding({
  makeTopFloor() {
    return {
      level: 1,
    };
  },
});
console.assert(house.topFloor.level === 1, 'topFloor works
  in house');
const skyScraper = generateBuilding({
  makeTopFloor() {
    return {
      level: 125,
    };
  },
});
console.assert(skyScraper.topFloor.level > 100, 'topFloor works in skyScraper');

One reason why using functions directly works better in JavaScript is that we didn’t have to implement a “throw an error to remind consumers to override me” makeFloor method that we had with the Building class.

In languages other than JavaScript that have support for abstract methods, this pattern is more useful and natural to implement than in JavaScript, where we have first-class functions.

You also have to bear in mind that the original versions of JavaScript/ECMAScript didn’t include a class construct.

In the final section of the chapter, we learned what the factory method pattern is and how it contrasts with the factory programming concept. We then implemented a class-based factory pattern scenario as well as a more idiomatic JavaScript version. Interspersed through this section, we covered the use cases, benefits, and drawbacks of the factory method pattern in JavaScript.