Abstract Factory

Known as a “factory of factories”, the abstract factory provides an interface for creating families of related or dependent objects without having to specify the type of concrete class.

TL;DR

A factory of factories; a factory that groups the individual but related/dependent factories together without specifying their concrete classes.

https://refactoring.guru/design-patterns/abstract-factory

Problem

Suppose that we have multiple restaurants of different cuisines, and each of them is able to serve a full meal in addition to delivering food. The full meal in this case is a 3-course meal, and so the process of ordering it at any specific location is the same (order meal get 3 courses). Since each restaurant type has its own process of creating each meal we need to find a way to centralize the creation of these meals without modifying the client interface.

Solution

Because each restaurant type provides a set of related objects (in this case they are all of the same cuisine), we can use an abstract factory to create a family of related products. This simply requires us to write a subclass of the abstract factory for each variant.

class CusineFactory(ABC):
	@abstractmethod
	def make_food(self) -> Meal:
		pass
 
class ItalianCuisineFactory(CusineFactory):
	def make_food(self) -> Meal:
		return ItalianMeal()

Note that this assumes a set of Factories are already implemented in order to make the specific meals. Another example is as follows, wherein the client code is simply build_army based on HumanArmy or OrcArmy. Each of those abstract factories will create archers and warriors that are related, while guaranteeing that orcs and humans do not mix.

from abc import ABC, abstractmethod
 
# Two types of units that must work together
class Warrior(ABC):
    @abstractmethod
    def attack(self) -> str:
        pass
 
class Archer(ABC):
    @abstractmethod
    def shoot(self) -> str:
        pass
 
# Human army family
class HumanWarrior(Warrior):
    def attack(self) -> str:
        return "Human warrior swings sword!"
 
class HumanArcher(Archer):
    def shoot(self) -> str:
        return "Human archer shoots arrow!"
 
# Orc army family  
class OrcWarrior(Warrior):
    def attack(self) -> str:
        return "Orc warrior smashes with club!"
 
class OrcArcher(Archer):
    def shoot(self) -> str:
        return "Orc archer hurls spear!"
 
# Abstract Factory
class ArmyFactory(ABC):
    @abstractmethod
    def create_warrior(self) -> Warrior:
        pass
    
    @abstractmethod
    def create_archer(self) -> Archer:
        pass
 
# Concrete Factories
class HumanArmyFactory(ArmyFactory):
    def create_warrior(self) -> Warrior:
        return HumanWarrior()
    
    def create_archer(self) -> Archer:
        return HumanArcher()
 
class OrcArmyFactory(ArmyFactory):
    def create_warrior(self) -> Warrior:
        return OrcWarrior()
    
    def create_archer(self) -> Archer:
        return OrcArcher()
 
# Client code
def build_army(factory: ArmyFactory):
    """Creates a matching army"""
    warrior = factory.create_warrior()
    archer = factory.create_archer()
    
    print(f"{warrior.attack()} | {archer.shoot()}")
 
# Demo
if __name__ == "__main__":
    print("Building Human Army:")
    build_army(HumanArmyFactory())
    
    print("Building Orc Army:")
    build_army(OrcArmyFactory())
    
    print("\n💡 Key point: Each factory creates a MATCHING ARMY of the same race!")