The Abstract Factory Pattern is a way to group the creation of related objects, like products of a certain brand or make by building a common factory interface. If this all sounds abstract, a picture can help:

A short breakdown:
- All the factories have a common interface called AbstractFactory
- The concrete factories, in our diagram there are two, implement this interface
- These concrete factories produce objects with a common interface so those are interchangeable. In our example those are called AbstractProductOne and AbstractProductTwo
This is a somewhat more complicated design pattern, but when used well can lead to interchangeable concrete implementations. However, considering the relative complexity of this pattern, it will require extra initial work. Also, due to the high abstraction levels, it can lead in some cases to code that is more difficult to test, debug and maintain.
So, even though this pattern is very flexible and powerful, use it wisely and sparingly.
Implementation in Python
For this example we will implement a VehicleFactory which produces two brands of vehicles, namely either Brand A or Brand B. It also produces two kinds of vehicles, either a car or a bike.
But first
from abc import ABC, abstractmethod
We will start by importing the following:
Why do we need these?
ABCstands for ‘Abstract Base Class’. This defines a special type of class which cannot be instantiated directly. Instead it serves as a blueprint or contract which other classes must follow. Think of it like a template enforcing certain rules. If a class inherites from anABCit must implement all of its abstract methods.- The
@abstractmethoddecorator marks methods as abstract within an ABC. Applying this decorator to a method declares the method has no implementation in the base class and must be overridden by a concrete subclass.
The interfaces
The CarInterface looks like this:
class CarInterface(ABC):
@abstractmethod
def description(self) -> str:
pass
Note that the CarInterface derives from ABC so it is an abstract base class, with one abstract method description()
All this interface provides is a way to retrieve the vehicle description.
The BikeInterface is similar:
class BikeInterface(ABC):
@abstractmethod
def description(self) -> str:
pass
Next we define the VehicleFactory interface. A factory in our example can only produce either a car or a bike:
class VehicleFactory(ABC):
@abstractmethod
def create_car(self,color:str) -> CarInterface:
pass
@abstractmethod
def create_bike(self,wheels:int) -> BikeInterface:
pass
Implementing the concrete classes
Next we need to define a concrete car BrandACar:
class BrandACar(CarInterface):
def __init__(self,color:str)->None:
self._color=color
self._make="Brand A"
def description(self) -> str:
return f"This is a {self._color} {self._make} Car."
A car just get its brand, which is always set to “Brand A” in this case, and a color, which can vary.
The BrandABike is similar. The difference is that with a bike, you can specify the number of wheels instead of the color:
class BrandABike(BikeInterface):
def __init__(self,wheels:int)->None:
self._numberOfWheels=wheels
self._make="Brand A"
def description(self) -> str:
return f"This is a {self._make} Bike with {self._numberOfWheels} wheels."
As you expected the BrandBCar and the BrandBBike are similarly fashioned:
class BrandBCar(CarInterface):
def __init__(self,color:str)->None:
self._color=color
self._make="Brand B"
def description(self) -> str:
return f"This is a {self._color} {self._make} Car."
class BrandBBike(BikeInterface):
def __init__(self,wheels:int)->None:
self._numberOfWheels=wheels
self._make="Brand B"
def description(self) -> str:
return f"This is a {self._make} Bike with {self._numberOfWheels} wheels."
Now we come to the heart of this pattern, the factory, in our case a BrandAVehicleFactory:
class BrandAVehicleFactory(VehicleFactory):
def create_car(self,color:str) -> CarInterface:
return BrandACar(color)
def create_bike(self,wheels:int) -> BikeInterface:
return BrandABike(wheels)
Some explanation is needed here:
- The
create_car()method returns aCarInterface. SinceBrandACarimplements that interface, the return statement is valid. - The same goes for the
create_bike()method. - The
BrandAFactoryderives fromVehicleFactorywhich provides the above two methods.
As you might expect, the BrandBVehicleFactory is built along similar lines:
class BrandBVehicleFactory(VehicleFactory):
def create_car(self,color:str) -> CarInterface:
return BrandBCar(color)
def create_bike(self,wheels:int) -> BikeInterface:
return BrandBBike(wheels)
Fetching the factory
Now we need a way to create the correct factory. We do this by setting up a simple registry. Start by setting up a dictionary like this:
VEHICLE_FACTORIES={
"BrandA": BrandAVehicleFactory(),
"BrandB": BrandBVehicleFactory(),
}
This dictionary has strings as its keys, and the two vehicle factories as values. However, both factories implement the VehicleFactory interface, so we can do this:
def get_vehicle_factory(brand: str) -> VehicleFactory:
factory = VEHICLE_FACTORIES.get(brand)
if not factory:
raise ValueError(f"Unknown brand: {brand}")
return factory
This function has one parameter, the brand, and looks in our very simple registry to see whether we have a factory registered for the brand, if not, an exception is raised. The function returns a VehicleFactory which means it can return either of the two concrete vehicle factories.
Time to test
Well, all of this is nice, but does it work:
if __name__ == "__main__":
brand_a_factory = get_vehicle_factory("BrandA")
brand_b_factory = get_vehicle_factory("BrandB")
car_a = brand_a_factory.create_car("Red")
bike_a = brand_a_factory.create_bike(2)
car_b = brand_b_factory.create_car("Blue")
bike_b = brand_b_factory.create_bike(3)
print(car_a.description())
print(bike_a.description())
print(car_b.description())
print(bike_b.description())
A line by line description:
- We start by trying to retrieve a brand A factory and a brand B factory.
- Next we create a car and a bike for each factory.
- Finally we print out the description.
Conclusion
Conclusion: Harnessing the Power of Abstract Factory
The Abstract Factory Pattern offers a robust solution for grouping the creation of related objects, ensuring that product families—like our Brand A and Brand B vehicles—are always compatible and interchangeable. We’ve seen through the Python implementation, utilizing Abstract Base Classes (ABCs) and interfaces for CarInterface, BikeInterface, and VehicleFactory, that this pattern enforces a clear structure and contract for object creation.
Key Takeaways
While implementing the pattern, even in this simple vehicle example, requires significant initial effort and a high degree of abstraction, the payoff is substantial. The resulting code benefits from increased flexibility and runtime configurability, allowing the client code to switch between entire product families (factories) without being tied to concrete classes.
However, as noted, this complexity is a double-edged sword. Developers must wield this powerful tool judiciously, being mindful of the potential for increased difficulty in testing, debugging, and maintenance. Ultimately, when applied thoughtfully, the Abstract Factory Pattern is an invaluable design tool that promotes cleaner architecture and aids in the development of highly adaptable software systems. Choose wisely when the overhead is justified by the need for interchangeable product families. 💡




