Factory Method Example
from abc import ABC, abstractmethod
from enum import Enum
from typing import Type
class ShipsEnum(Enum):
MILLENIUM_FALCON = "MilleniumFalcon"
UNSC_INFINITY = "UNSCInfinity"
USS_ENTERPRISE = "USSEnterprise"
SERENITY = "Serenity"
class SpaceShipFactory:
_registry: dict[ShipsEnum, Type["SpaceShip"]] = {}
@classmethod
def register(cls, ship_type: ShipsEnum, ship_class: Type["SpaceShip"]):
cls._registry[ship_type] = ship_class
@classmethod
def create_ship(
cls,
ship_type: ShipsEnum,
position: tuple[int, int],
size: tuple[int, int],
display_name: str,
speed: int = 0,
) -> "SpaceShip":
try:
ship_class = cls._registry[ship_type]
return ship_class(position, size, display_name, speed)
except KeyError:
raise ValueError(f"Unknown ship type: {ship_type}")
class SpaceShip(ABC):
def __init__(
self,
position: tuple[int, int],
size: tuple[int, int],
display_name: str,
speed: int = 0,
):
self.position = position
self.size = size
self.display_name = display_name
self.speed = speed
@abstractmethod
def fly(self) -> str:
pass
@abstractmethod
def shoot(self) -> str:
pass
class MilleniumFalcon(SpaceShip):
def fly(self):
return "The Millenium Falcon is flying through hyperspace!"
def shoot(self):
return "The Millenium Falcon fires its blasters!"
SpaceShipFactory.register(ShipsEnum.MILLENIUM_FALCON, MilleniumFalcon)
class UNSCInfinity(SpaceShip):
def fly(self):
return "The UNSC Infinity is cruising through space!"
def shoot(self):
return "The UNSC Infinity launches its MAC rounds!"
SpaceShipFactory.register(ShipsEnum.UNSC_INFINITY, UNSCInfinity)
class USSEnterprise(SpaceShip):
def fly(self):
return "The USS Enterprise is exploring the galaxy!"
def shoot(self):
return "The USS Enterprise fires its phasers!"
SpaceShipFactory.register(ShipsEnum.USS_ENTERPRISE, USSEnterprise)
class Serenity(SpaceShip):
def fly(self):
return "The Serenity is gliding through the 'verse!"
def shoot(self):
return "The Serenity fires its pulse cannons!"
SpaceShipFactory.register(ShipsEnum.SERENITY, Serenity)
Brief explaination
Why Using a Registry Is Better Than Using Many if/elif Conditions
A conditional factory looks like this:
if ship_type == ShipsEnum.MILLENIUM_FALCON:
return MilleniumFalcon(...)
elif ship_type == ShipsEnum.UNSC_INFINITY:
return UNSCInfinity(...)
elif ship_type == ShipsEnum.USS_ENTERPRISE:
return USSEnterprise(...)
...
This works, but it has a big problem: every time you add a new ship, you must edit the factory and add a new elif.
This makes the factory grow endlessly and violates the Open/Closed Principle (your code should be open for extension, but closed for modification).
Infact, the conditional factory must be modified every time you add a ship.
The Registry Pattern Fixes the Problem: instead of writing conditionals, we store ship classes inside a dictionary:
_registry = {
ShipsEnum.MILLENIUM_FALCON: MilleniumFalcon,
ShipsEnum.UNSC_INFINITY: UNSCInfinity,
ShipsEnum.USS_ENTERPRISE: USSEnterprise,
ShipsEnum.SERENITY: Serenity,
}
Now the factory becomes much simpler:
ship_class = cls._registry[ship_type]
return ship_class(position, size, display_name, speed)
Why is this better?
Because you no longer modify the factory logic: you only update the registry, not the function that creates objects.
This keeps the factory small, clean, and scalable.
The Core Idea
The factory no longer needs to know anything about the ships.
It just:
-
Looks up the class
-
Instantiates the object
The creation logic never changes.
Adding a New Ship Using a Registry
With the old method, you needed to modify 3 places.
With a registry, you only add this:
SpaceShipFactory._registry[ShipsEnum.NEW_SHIP] = NewShipClass
No conditions. No modifications to the factory logic.
Auto-Registration
You can let ships register themselves:
class SpaceShipFactory:
registry = {}
@classmethod
def register(cls, enum, ship_class):
cls.registry[enum] = ship_class
Then each ship registers itself:
class MilleniumFalcon(SpaceShip):
...
SpaceShipFactory.register(ShipsEnum.MILLENIUM_FALCON, MilleniumFalcon)