Design Pattern: Abstract Factory Pattern in TypeScript

We can generate multiple factories using the Abstract factory pattern. To understand the Abstract factory pattern, make sure that you understand the Factory pattern before Abstract Factory pattern.

This article discusses the Abstract Factory pattern implementations in TypeScript. Check the implementation details and examples.

Implementation

Use the following steps for the implementation-

  • Create an interface for the item classes.
  • Create the item classes and implemented the item interface.
  • Create an interface for the factories. Define a method that will accept the identifier, which will return item objects.
  • Create factory classes and implement the factory interface. In the method that is responsible for objects generate, check the identifier, and then create a new item object and return that.
  • Create a producer, that will accept an identifier, and based on that return a factory object.
  • While using the implement use the producer to generate a factory and then use that factory to generate item objects.

Here is a simple Abstract factory implementation in TypeScript. This is not any specific example, just demo/sample code.

// --- Item interface start ---

interface ItemInterface {
    operation1(): void;
    operation2(): void;
}

// --- Item interface end ---

// --- Type1 item classes start ---

class Type1Item1 implements ItemInterface {
    operation1(): void {
        console.log("Type1Item1: executing operation 1");
    }

    operation2(): void {
        console.log("Type1Item1: executing operation 2");
    }
}

class Type1Item2 implements ItemInterface {
    operation1(): void {
        console.log("Type1Item2: executing operation 1");
    }

    operation2(): void {
        console.log("Type1Item2: executing operation 2");
    }
}

// --- Type1 item classes end ---

// --- Type2 item classes start ---

class Type2Item1 implements ItemInterface {
    operation1(): void {
        console.log("Type2Item1: executing operation 1");
    }

    operation2(): void {
        console.log("Type2Item1: executing operation 2");
    }
}

class Type2Item2 implements ItemInterface {
    operation1(): void {
        console.log("Type2Item2: executing operation 1");
    }

    operation2(): void {
        console.log("Type2Item2: executing operation 2");
    }
}

// --- Type2 item classes end ---

// --- Factory interface start ---

interface FactoryInterface {
    getItem(type: string): (ItemInterface | null);
}

// --- Factory interface end --- 

// --- Factory classes start ---

class Type1Factory implements FactoryInterface {
    getItem(itemIdentifier: string): (ItemInterface | null) {
        if (itemIdentifier.toLowerCase() === "item1") {
            return new Type1Item1();
        }

        if (itemIdentifier.toLowerCase() === "item2") {
            return new Type1Item2();
        }

        return null;
    }
}

class Type2Factory implements FactoryInterface {
    getItem(itemIdentifier: string): (ItemInterface | null) {
        if (itemIdentifier.toLowerCase() === "item1") {
            return new Type2Item1();
        }

        if (itemIdentifier.toLowerCase() === "item2") {
            return new Type2Item2();
        }

        return null;
    }
}

// --- Factory classes end ---

// --- Facotyr producer start ---

class FactoryProducer {
    static getFactory(type: number): (FactoryInterface | null) {
        if (type == 1) {
            return new Type1Factory();
        }

        if (type == 2) {
            return new Type2Factory();
        }

        return null;
    }
}

// --- Factory producer end ---

// --- Demo start ---

const factory1 = FactoryProducer.getFactory(1);

const item1 = factory1?.getItem("item1");
item1?.operation1();
item1?.operation2();

const factory2 = FactoryProducer.getFactory(2);

const item2 = factory2?.getItem("item1");
item2?.operation1();
item2?.operation2();

const item3 = factory2?.getItem("item2");
item3?.operation1();
item3?.operation2();

// --- Demo end ---

Above code will generate the following output:

Type1Item1: executing operation 1
Type1Item1: executing operation 2

Type2Item1: executing operation 1
Type2Item1: executing operation 2

Type2Item2: executing operation 1
Type2Item2: executing operation 2

Examples

Let’s look at a few examples of Abstract factory pattern implementation in TypeScript.

Example #1: Transport

In this example, we have a few item classes which represent different vehicles. Some of the vehicles are 2 wheelers and some are 4 wheelers.

We are going to implement 2 factories for those 2 types of vehicles. Then we will implement a producer that will combine those 2 types of factories.

Transport Interface [Item Interface]

  • Create file “transport.ts”.
  • Define interface “Transport”. Declare method as per requirement, here we have declared 2 methods – “start”, “stop”, and “repair”.
// transport.ts

interface Transport {
    start(): void;
    stop(): void;
    repair(): void;
}

export default Transport;

Bicycle Class [Item Class]

  • Create a file named “bicycle.ts”.
  • Create class “Bicycle” and implement the interface “Transport”. Define methods “start”, “stop” and “repair” as part of the interface implementation.
// bicycle.ts

import Transport from "./transport";

class Bicycle implements Transport {
    start(): void {
        console.log("Bicycle Started");
    }
    stop(): void {
        console.log("Bicycle Stopped");
    }
    repair(): void {
        console.log("Bicycle Repair");
    }
}


export default Bicycle;

Motorcycle Class [Item Class]

  • Create a file named “motorcycle.ts”.
  • Create class “Motorcycle”. Implement interface “Transport” for the class.
// motorcycle.ts

import Transport from "./transport";

class Motorcycle implements Transport {
    start(): void {
        console.log("Motorcycle Started");
    }
    stop(): void {
        console.log("Motorcycle Stopped");
    }
    repair(): void {
        console.log("Motorcycle Repair");
    }
}


export default Motorcycle;

Car Class [Item Class]

  • Create a file named “car.ts”.
  • Define “Car” class and implement the “Transport” interface for the class.
// car.ts

import Transport from "./transport";

class Car implements Transport {
    start(): void {
        console.log("Car Started");
    }
    stop(): void {
        console.log("Car Stopped");
    }
    repair(): void {
        console.log("Car Repair");
    }
}


export default Car;

Truck Class [Item Class]

  • Create a file named “truck.ts”.
  • Define item class “Truck” and implement “Transport” interface.
// truck.ts

import Transport from "./transport";


class Truck implements Transport {
    start(): void {
        console.log("Truck Started");
    }

    stop(): void {
        console.log("Truck Stopped");
    }

    repair(): void {
        console.log("Truck Repair");
    }
}

export default Truck;

Abstract Transport Factory Interface [Factory Interface]

  • Create a file named “abstract-transport-factory.ts”.
  • Declare interface “AbstractTransportFactory”, which will be implemented for the factory classes.
  • Declare a method for returning the item class. Here we have named it “getTransport”. This method accepts a param for the identifier and returns an item object of type “Transport”.

This can be an abstract class, if required.

// abstract-transport-factory.ts

import Transport from "./transport";

interface AbstractTransportFactory {
    getTransport(type: string): (Transport | null);
}

export default AbstractTransportFactory;

Two-Wheel Transport Factory Class [Factory Class]

  • Create a file named “two-wheel-transport-factory.ts”.
  • Define class “TwoWheelTransportFactory”.
  • Implement the “AbstractTransportFactory” interface for the class.
// two-wheel-transport-factory.ts

import AbstractTransportFactory from "./abstract-transport-factory";
import Bicycle from "./bicycle";
import Motorcycle from "./motorcycle";
import Transport from "./transport";

class TwoWheelTransportFactory implements AbstractTransportFactory {
    getTransport(type: string): (Transport | null) {
        if (type.toLowerCase() === "bicycle") {
            return new Bicycle();
        }

        if (type.toLowerCase() === "motorcycle") {
            return new Motorcycle();
        }

        return null;
    }
}

export default TwoWheelTransportFactory;

Four-Wheel Transport Factory Class [Factory Class]

  • Create a file named “four-wheel-transport-factory.ts”.
  • Define factory class “FourWheelTransportFactory”, and implement “AbstractTransportFactory” for the class.
// four-wheel-transport-factory.ts

import AbstractTransportFactory from "./abstract-transport-factory";
import Car from "./car";
import Transport from "./transport";
import Truck from "./truck";

class FourWheelTransportFactory implements AbstractTransportFactory {
    getTransport(type: string): (Transport | null) {
        if (type.toLowerCase() === "car") {
            return new Car();
        }

        if (type.toLowerCase() === "truck") {
            return new Truck();
        }

        return null;
    }
}

export default FourWheelTransportFactory;

Factory Producer Class [Producer]

  • Create a file named “factory-producer.ts”.
  • Define class “FactoryProducer”.
  • Add method “getFactory”. This method accepts an identifier “numberOfWheels”. Based on the identifier it generates a new object of the class “TwoWheelTransportFacory” or “FourWheelTransportFactory”. This method is declared as “static”.
// factory-producer.ts

import AbstractTransportFactory from "./abstract-transport-factory";
import FourWheelTransportFactory from "./four-wheel-transport-factory";
import TwoWheelTransportFactory from "./two-wheel-transport-factory";

class FactoryProducer {
    static getFactory(numberOfWheels: number): (AbstractTransportFactory | null) {
        if (numberOfWheels == 2) {
            return new TwoWheelTransportFactory();
        }

        if (numberOfWheels == 4) {
            return new FourWheelTransportFactory();
        }

        return null;
    }
}

export default FactoryProducer;

Demo

  • Create a file named “demo.ts”.
  • Call method “GetFactory” from “FactoryProducer” and get an instance of factory object (any of the “TwoWheelTransportFactory” or “FourWheelTransportFactory”.
  • Call the “getTransport” method to get an instance of the item class.
  • Use the item object obtained from the factory.
// demo.ts

import FactoryProducer from "./factory-producer";

const transportFactory1 = FactoryProducer.getFactory(2);

const transport1 = transportFactory1?.getTransport("bicycle");
transport1?.start();

const transportFactory2 = FactoryProducer.getFactory(4);

const transport2 = transportFactory2?.getTransport("truck");
transport2?.start();
transport2?.stop();

Output

Here is the output of the demo code.

Bicycle Started

Truck Started
Truck Stopped

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Abstract Factory pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.