The Dev Journey

Abstract Factory Pattern using Typescript

The Abstract Factory pattern is a creational design pattern. It provides an interface for creating families of related or dependent objects without specifying their concrete classes. It allows you to create objects that are related to each other by ensuring that the created objects are compatible. This pattern is particularly useful when a system must be independent of how its products are created, composed, and represented.

Key Components

  1. AbstractFactory: Declares an interface for operations that create abstract product objects.
  2. ConcreteFactory: Implements the operations declared in the AbstractFactory interface to create concrete product objects.
  3. AbstractProduct: Declares an interface for a type of product object.
  4. ConcreteProduct: Implements the AbstractProduct interface.
  5. Client: Uses the interfaces declared by AbstractFactory and AbstractProduct classes.

Example in TypeScript

Let’s consider an example where we have a furniture factory that produces different types of furniture (chairs and sofas) in different styles (modern and Victorian).

// AbstractProductA: Chair
interface Chair {
    hasLegs(): boolean;
    sitOn(): string;
}

// AbstractProductB: Sofa
interface Sofa {
    hasLegs(): boolean;
    lieOn(): string;
}

// ConcreteProductA1: ModernChair
class ModernChair implements Chair {
    hasLegs(): boolean {
        return true;
    }
    sitOn(): string {
        return "Sitting on a modern chair.";
    }
}

// ConcreteProductA2: VictorianChair
class VictorianChair implements Chair {
    hasLegs(): boolean {
        return true;
    }
    sitOn(): string {
        return "Sitting on a Victorian chair.";
    }
}

// ConcreteProductB1: ModernSofa
class ModernSofa implements Sofa {
    hasLegs(): boolean {
        return true;
    }
    lieOn(): string {
        return "Lying on a modern sofa.";
    }
}

// ConcreteProductB2: VictorianSofa
class VictorianSofa implements Sofa {
    hasLegs(): boolean {
        return true;
    }
    lieOn(): string {
        return "Lying on a Victorian sofa.";
    }
}

// AbstractFactory
interface FurnitureFactory {
    createChair(): Chair;
    createSofa(): Sofa;
}

// ConcreteFactory1: ModernFurnitureFactory
class ModernFurnitureFactory implements FurnitureFactory {
    createChair(): Chair {
        return new ModernChair();
    }
    createSofa(): Sofa {
        return new ModernSofa();
    }
}

// ConcreteFactory2: VictorianFurnitureFactory
class VictorianFurnitureFactory implements FurnitureFactory {
    createChair(): Chair {
        return new VictorianChair();
    }
    createSofa(): Sofa {
        return new VictorianSofa();
    }
}

// Client
class Client {
    private chair: Chair;
    private sofa: Sofa;

    constructor(factory: FurnitureFactory) {
        this.chair = factory.createChair();
        this.sofa = factory.createSofa();
    }

    run(): void {
        console.log(this.chair.sitOn());
        console.log(this.sofa.lieOn());
    }
}

// Usage
const modernFactory = new ModernFurnitureFactory();
const victorianFactory = new VictorianFurnitureFactory();

const modernClient = new Client(modernFactory);
modernClient.run();

const victorianClient = new Client(victorianFactory);
victorianClient.run();

Rules to Remember

  1. Family of Products: The Abstract Factory pattern is used to create families of related or dependent objects. This means that the objects created by the factory are designed to work together.
  2. Interface for Creation: The pattern provides an interface for creating objects, but it allows subclasses to alter the type of objects that will be created. This means that the client code interacts with the factory through an abstract interface, and the concrete factory classes determine the specific types of objects to create.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *