Card 03/ 08
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.
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.
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.
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.
def client(factory: AbstractFactory) -> None:
a = factory.create_product_a()
b = factory.create_product_b()
print(b.works_with(a))The complete template
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())B1 works with A1
B2 works with A2public 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(); }
}B1 works with A1
B2 works with A2interface 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());B1 works with A1
B2 works with A2Each 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.
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__)ProductA2 ProductB2← → move through sections and cards