[Design Pattern] Creational Patterns - Abstract Factory

·

4 min read

[Design Pattern] Creational Patterns - Abstract Factory

Basic Concepts

Various Meanings of Factory

  • A function or method that generates a graphical user interface for a program.

  • A class that creates users.

  • A static method that calls the class constructor in a specific way.

  • One of the creational design patterns.

Let's think of it as an interface for now!

Interface vs Class

Interface: Defines the attributes and methods that an object should have. The goal is to create an interface.
Class: Implements the interface to provide the actual implementation. It is the tool for creating the interface.

Abstract Factory

Meaning

The Abstract Factory pattern is a design pattern that abstracts the object creation process. By separating the code that handles object creation from the code that actually creates objects, this pattern makes it easy to create and modify new objects.

In the Abstract Factory pattern, concrete classes refer to specifically implemented classes. Instead of directly specifying concrete classes in the Abstract Factory pattern, object creation is abstracted using factory classes. These abstracted factory classes provide interfaces for creating related families of products and maintain consistency among objects within the product family.

Therefore, by using the Abstract Factory pattern, when adding new product families or modifying existing ones, the modification of object creation code can be minimized.

Example

Product families and their variants.

  1. A customer places an order for a Victorian chair.

2. The customer orders a sofa that goes with the Victorian chair, but a modern sofa is delivered instead. 3. In this case, a method is needed to ensure that newly created furniture objects have matching variations (styles) with other furniture objects in the same family. There are two reasons for this:

  1. The customer may be disappointed if they receive a set of furniture that does not match the style.

  2. Furniture suppliers often change their catalogs, so they may have the inconvenience of having to modify existing code every time they add new products or product families.

  3. The Abstract Factory design pattern can be used to solve this problem.

  4. First, define separate interfaces for each product family. In our example, define the Chair interface that defines the methods required to create a chair.

6. Define an interface that oversees all products and product families. In our example, define the FurnitureFactory interface that defines methods for creating each type of furniture. 7. With the Abstract Factory, when the client orders a chair from the furniture factory, the client doesn't need to know the classes of the furniture factory or worry about what style of chair the factory will create. Instead, the client uses the Chair interface to order all chairs in the same way, regardless of whether they are modern chairs or Victorian chairs, and the created chair will always be created with a sofa or coffee table that matches the furniture style family.

TypeScript Code

Compare the code and the diagram to understand the Abstract Factory.

interface AbstractFactory {
    createProductA(): AbstractProductA;

    createProduct

B(): AbstractProductB;
}

class ConcreteFactory1 implements AbstractFactory {
    public createProductA(): AbstractProductA {
        return new ConcreteProductA1();
    }

    public createProductB(): AbstractProductB {
        return new ConcreteProductB1();
    }
}

class ConcreteFactory2 implements AbstractFactory {
    public createProductA(): AbstractProductA {
        return new ConcreteProductA2();
    }

    public createProductB(): AbstractProductB {
        return new ConcreteProductB2();
    }
}

interface AbstractProductA {
    usefulFunctionA(): string;
}

class ConcreteProductA1 implements AbstractProductA {
    public usefulFunctionA(): string {
        return 'The result of the product A1.';
    }
}

class ConcreteProductA2 implements AbstractProductA {
    public usefulFunctionA(): string {
        return 'The result of the product A2.';
    }
}

function clientCode(factory: AbstractFactory) {
    const productA = factory.createProductA();
    const productB = factory.createProductB();
}

  • AbstractFactory is an interface, and ConcreteFactory1 class implements that interface.

  • Therefore, ConcreteFactory1 must implement all the methods required by the AbstractFactory interface. In this pattern, AbstractFactory typically represents a class called "Abstract Factory" that provides methods for creating families of related objects.

  • The ConcreteFactory1 class satisfies the requirements of the AbstractFactory interface and creates instances of the concrete classes (ConcreteProductA1 and ConcreteProductB1) that implement AbstractProductA and AbstractProductB, respectively.

  • In this case, the client can only communicate with the top-level AbstractFactory.

Class Flow

When to Use Abstract Factory Pattern

  • When working with various related product families that need to operate together and you don't want to depend on specific classes of the product.

  • When you need to select one of several product families to configure the system and may need to switch to a different variant of the product once configured.

  • When you provide a library of classes for products, and you want to expose only their interfaces, not their implementations.

Advantages of the Abstract Factory

  • Ensures the compatibility of products generated by the factory.

  • Avoids tight coupling between concrete products and client code.

  • Single Responsibility Principle: Extracting product creation code to one place makes the code easier to maintain.

  • Open/Closed Principle: Can create new variations of products without modifying existing client code.