Card 03/ 08

CodeDifficulty: Intermediate4 min

Abstract Factory Template in Python, Java and TypeScript

Two product kinds, two families, and a client that never names a concrete class.

The Abstract Factory template is the pattern in its pure form: two kinds of product, A and B, in two families, 1 and 2, with the participant names from the class diagram. The steps add each participant in Python, the tabs give the complete program in three languages, and the card ends on the shorter form Python often uses instead.

Building the template one participant at a time

Abstract products

One interface per kind of product. Product B can work with an A, which is what makes the two a family.

py
class AbstractProductA(ABC):
    @abstractmethod
    def name(self) -> str: ...


class AbstractProductB(ABC):
    @abstractmethod
    def works_with(self, partner: AbstractProductA) -> str: ...

Concrete products

Four classes: ProductA1, ProductA2, ProductB1 and ProductB2, each implementing its kind's interface.

Abstract factory

One creation method for each kind of product.

py
class AbstractFactory(ABC):
    @abstractmethod
    def create_product_a(self) -> AbstractProductA: ...

    @abstractmethod
    def create_product_b(self) -> AbstractProductB: ...

Concrete factories

Each family's factory returns only that family's products.

py
class ConcreteFactory1(AbstractFactory):
    def create_product_a(self) -> AbstractProductA:
        return ProductA1()

    def create_product_b(self) -> AbstractProductB:
        return ProductB1()

Client

The client receives a factory and uses abstract types alone.

py
def client(factory: AbstractFactory) -> None:
    a = factory.create_product_a()
    b = factory.create_product_b()
    print(b.works_with(a))

The complete template

py
from abc import ABC, abstractmethod


class AbstractProductA(ABC):
    @abstractmethod
    def name(self) -> str: ...


class AbstractProductB(ABC):
    @abstractmethod
    def works_with(self, partner: AbstractProductA) -> str: ...


class ProductA1(AbstractProductA):
    def name(self) -> str:
        return "A1"


class ProductA2(AbstractProductA):
    def name(self) -> str:
        return "A2"


class ProductB1(AbstractProductB):
    def works_with(self, partner: AbstractProductA) -> str:
        return f"B1 works with {partner.name()}"


class ProductB2(AbstractProductB):
    def works_with(self, partner: AbstractProductA) -> str:
        return f"B2 works with {partner.name()}"


class AbstractFactory(ABC):
    @abstractmethod
    def create_product_a(self) -> AbstractProductA: ...

    @abstractmethod
    def create_product_b(self) -> AbstractProductB: ...


class ConcreteFactory1(AbstractFactory):
    def create_product_a(self) -> AbstractProductA:
        return ProductA1()

    def create_product_b(self) -> AbstractProductB:
        return ProductB1()


class ConcreteFactory2(AbstractFactory):
    def create_product_a(self) -> AbstractProductA:
        return ProductA2()

    def create_product_b(self) -> AbstractProductB:
        return ProductB2()


def client(factory: AbstractFactory) -> None:
    a = factory.create_product_a()
    b = factory.create_product_b()
    print(b.works_with(a))


client(ConcreteFactory1())
client(ConcreteFactory2())
OutputRan with python:3.12-slim (Python 3.12.13)
B1 works with A1
B2 works with A2
The factory interface, the client's two requests, and the one line per family that chooses.
java
public class AfTemplate {
    static void client(AbstractFactory factory) {
        AbstractProductA a = factory.createProductA();
        AbstractProductB b = factory.createProductB();
        System.out.println(b.worksWith(a));
    }

    public static void main(String[] args) {
        client(new ConcreteFactory1());
        client(new ConcreteFactory2());
    }
}

interface AbstractProductA {
    String name();
}

interface AbstractProductB {
    String worksWith(AbstractProductA partner);
}

class ProductA1 implements AbstractProductA {
    public String name() { return "A1"; }
}

class ProductA2 implements AbstractProductA {
    public String name() { return "A2"; }
}

class ProductB1 implements AbstractProductB {
    public String worksWith(AbstractProductA partner) { return "B1 works with " + partner.name(); }
}

class ProductB2 implements AbstractProductB {
    public String worksWith(AbstractProductA partner) { return "B2 works with " + partner.name(); }
}

interface AbstractFactory {
    AbstractProductA createProductA();
    AbstractProductB createProductB();
}

class ConcreteFactory1 implements AbstractFactory {
    public AbstractProductA createProductA() { return new ProductA1(); }
    public AbstractProductB createProductB() { return new ProductB1(); }
}

class ConcreteFactory2 implements AbstractFactory {
    public AbstractProductA createProductA() { return new ProductA2(); }
    public AbstractProductB createProductB() { return new ProductB2(); }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
B1 works with A1
B2 works with A2
Java places the client first so the source launcher finds `main`; the roles are the same.
ts
interface AbstractProductA {
  name(): string;
}

interface AbstractProductB {
  worksWith(partner: AbstractProductA): string;
}

class ProductA1 implements AbstractProductA {
  name(): string { return "A1"; }
}

class ProductA2 implements AbstractProductA {
  name(): string { return "A2"; }
}

class ProductB1 implements AbstractProductB {
  worksWith(partner: AbstractProductA): string { return `B1 works with ${partner.name()}`; }
}

class ProductB2 implements AbstractProductB {
  worksWith(partner: AbstractProductA): string { return `B2 works with ${partner.name()}`; }
}

interface AbstractFactory {
  createProductA(): AbstractProductA;
  createProductB(): AbstractProductB;
}

class ConcreteFactory1 implements AbstractFactory {
  createProductA(): AbstractProductA { return new ProductA1(); }
  createProductB(): AbstractProductB { return new ProductB1(); }
}

class ConcreteFactory2 implements AbstractFactory {
  createProductA(): AbstractProductA { return new ProductA2(); }
  createProductB(): AbstractProductB { return new ProductB2(); }
}

function client(factory: AbstractFactory): void {
  const a = factory.createProductA();
  const b = factory.createProductB();
  console.log(b.worksWith(a));
}

client(new ConcreteFactory1());
client(new ConcreteFactory2());
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
B1 works with A1
B2 works with A2
Interfaces for both factory and products, since TypeScript checks shape rather than inheritance.

Each family prints a matching pair, B1 works with A1 and B2 works with A2, and the client's code is the same for both. The only line that names a family is the call that passes a concrete factory, so changing family is one edit in one place.

Python often writes the same idea with less ceremony. Classes are values, so a family can be a dictionary from kind to class, and choosing a family is a lookup. It keeps the guarantee that products come from one family, and gives up the type checker's view of what a factory must provide.

py
class ProductA1: ...
class ProductB1: ...
class ProductA2: ...
class ProductB2: ...


FAMILIES = {
    "family1": {"a": ProductA1, "b": ProductB1},
    "family2": {"a": ProductA2, "b": ProductB2},
}

factory = FAMILIES["family2"]
print(type(factory["a"]()).__name__, type(factory["b"]()).__name__)
OutputRan with python:3.12-slim (Python 3.12.13)
ProductA2 ProductB2
A family as a dictionary of classes.