Creational Design Patterns Part 2: Abstract Factory Method

Creational Design Patterns Part 2: Abstract Factory Method

In the previous post, we explored the Factory Method design pattern, when to use it, and how to implement it in JavaScript. In this post, we will dive deeper into the abstract factory pattern, which is a more advanced version of the Factory Method pattern.

What is the Abstract Factory Pattern?

The Abstract Factory pattern is another creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. In other words, it allows you to create a set of related objects that share a common theme, without specifying their exact types. The pattern provides a way to encapsulate a group of individual factories (called families) that have a common theme or purpose.

The main idea behind the Abstract Factory pattern is to create a factory of factories. This means that instead of having a single factory method that creates one type of object, we have a set of factory methods that create a set of related objects. Each factory method creates a set of objects that belong to the same family.

Imagine a furniture store that sells different types of furniture, such as chairs, tables, and sofas. The store has several collections of furniture, each with its own theme, such as modern, classic, and rustic. Each collection has its own set of related furniture items, such as a modern sofa, a modern coffee table, and a modern dining chair. In this sense, the store is like an Abstract Factory. Each collection of furniture is like a family of related objects, and each piece of furniture is like a specific object created by a factory method.

Why use the Abstract Factory Pattern?

The Abstract Factory Pattern can be useful when:

  • We want to create families of related objects without specifying their concrete classes

  • We want to provide a way to create objects that are related or dependent on each other

  • We want to hide the details of object creation and only expose a high-level interface

  • We want to allow the application to be easily extended to create new families of objects

How to implement the Abstract Factory Pattern in JavaScript?

To implement the Abstract Factory pattern in JavaScript, we need to define an abstract factory class that declares a set of abstract factory methods. Each factory method should return an object of a certain type (or an interface). Then, we need to define concrete subclasses that extend the abstract factory class and implement the factory methods. Each subclass should create a different set of related objects that belong to the same family.

Let's see an example of how we can use the Abstract Factory pattern to create different types of user interfaces:

// Define a Car class
class Car {
  constructor({ make, model, year, color }) {
    this.make = make;
    this.model = model;
    this.year = year;
    this.color = color;
  }

  getDetails() {
    return `Make: ${this.make}, Model: ${this.model}, Year: ${this.year}, Color: ${this.color}`;
  }
}

// Define a Factory Method for creating economy cars
function createEconomyCar(make, model, year, color) {
  return new Car({ make, model, year, color, type: 'economy' });
}

// Define a Factory Method for creating sports cars
function createSportsCar(make, model, year, color) {
  return new Car({ make, model, year, color, type: 'sports' });
}

// Define an Abstract Factory for creating cars
class CarFactory {
  createEconomyCar(make, model, year, color) {
    throw new Error("Method 'say()' must be implemented.");
  }
  createSportsCar(make, model, year, color) {
    throw new Error("Method 'say()' must be implemented.");
  }
}

// Define a Concrete Factory that creates cars for a specific market
class USACarFactory extends CarFactory {
  createEconomyCar(make, model, year, color) {
    return createEconomyCar(make, model, year, color);
  }

  createSportsCar(make, model, year, color) {
    return createSportsCar(make, model, year, color);
  }
}

// Define a Concrete Factory that creates cars for another market
class EuropeCarFactory extends CarFactory {
  createEconomyCar(make, model, year, color) {
    return createEconomyCar(make, model, year, color);
  }

  createSportsCar(make, model, year, color) {
    // In Europe, sports cars have different specifications
    return new Car({ make, model, year, color, type: 'sports', drivetrain: 'AWD' });
  }
}

// Create a factory for cars in the USA
const usaFactory = new USACarFactory();

// Create a factory for cars in Europe
const europeFactory = new EuropeCarFactory();

// Use the factories to create cars
const car1 = usaFactory.createEconomyCar('Toyota', 'Corolla', 2022, 'blue');
const car2 = europeFactory.createSportsCar('Ford', 'Mustang', 2021, 'red');

// Display the details of each car
console.log(car1.getDetails());
console.log(car2.getDetails());

In this code example, we define a Car class that represents a car object, with properties such as make, model, year, color, and type. We also define two Factory Methods, createEconomyCar() and createSportsCar(), which create cars of different types.

We then define an Abstract Factory, CarFactory, which provides an interface for creating different types of cars for a specific market. This factory has two abstract methods, createEconomyCar() and createSportsCar(), which are implemented by the Concrete Factories, USACarFactory and EuropeCarFactory. These Concrete Factories create cars of specific types for their respective markets using the Factory Methods.

We create a factory for cars in the USA and a factory for cars in Europe, and use these factories to create two cars. This demonstrates how the Abstract Factory pattern can be used to create families of related objects in a flexible and modular way, allowing for easy addition or modification of different types of cars for different markets.

Conclusion

The Abstract Factory pattern is a powerful tool for creating families of related objects without specifying their concrete classes. By encapsulating a group of individual factories that have a common theme or purpose, the pattern allows for a high-level interface that hides the details of object creation. It also enables the application to be easily extended to create new families of objects. In JavaScript, the pattern can be implemented by defining an abstract factory class that declares a set of abstract factory methods and concrete subclasses that extend the abstract factory class and implement the factory methods. Overall, the Abstract Factory pattern can help make your code more modular, maintainable, and extensible.