Abstract Factory
One object that creates a whole family of matching parts, so you can never mix pieces from two families by accident.
Big idea
An Abstract Factory is one object with a creation method per part of a family — create_storage(), create_queue(). Each concrete factory builds parts that belong together. Client code asks the factory it was given and can't accidentally combine parts from two families.
The problem it solves
A service runs on two environments: in the cloud it uses cloud blob storage and a managed queue; on a developer's laptop it uses a local folder and an in-memory queue. Code is sprinkled with:
store = CloudStorage() if env == "cloud" else LocalStorage()
queue = CloudQueue() if env == "cloud" else MemoryQueue()
Each pair of ifs is a chance to mismatch — one forgotten branch and the laptop build writes to production storage. Adding a third environment means finding every such pair.
Think of it like
Flat-pack furniture ranges in a home store. You pick a range — say, the oak range — and the bed, the wardrobe, and the bedside table all come in matching oak with matching handles. You don't pick each piece's style separately and hope they match; choosing the range picks them all.
Structure
In code
class CloudStorage:
def put(self, key, data): return f"cloud-blob://{key} ({len(data)} bytes)"
class CloudQueue:
def push(self, msg): return f"cloud-queue <- {msg}"
class FolderStorage:
def put(self, key, data): return f"./data/{key} ({len(data)} bytes)"
class MemoryQueue:
def push(self, msg): return f"memory-queue <- {msg}"
class CloudFactory:
def create_storage(self): return CloudStorage()
def create_queue(self): return CloudQueue()
class LocalFactory:
def create_storage(self): return FolderStorage()
def create_queue(self): return MemoryQueue()
class UploadService: # only knows the factory interface
def __init__(self, factory):
self.storage = factory.create_storage()
self.queue = factory.create_queue()
def upload(self, key, data):
return [self.storage.put(key, data), self.queue.push(f"uploaded {key}")]
FACTORIES = {"cloud": CloudFactory, "local": LocalFactory} # chosen once, at startup
for env in ("local", "cloud"):
for line in UploadService(FACTORIES[env]()).upload("cat.png", b"\x89PNG...."):
print(f"{env:5} {line}")output
local ./data/cat.png (8 bytes)
local memory-queue <- uploaded cat.png
cloud cloud-blob://cat.png (8 bytes)
cloud cloud-queue <- uploaded cat.pngRun and edit it in the app.
The environment is decided in one line at startup. UploadService can't end up with cloud storage and a memory queue, because it never chooses parts individually.
Watch it
How to implement it
- List the product kinds (storage, queue) and the families (cloud, local) — a grid.
- Give each product kind an interface.
- Declare an abstract factory with one
create_x()per product kind. - Write one concrete factory per family.
- Choose the concrete factory once — in configuration or
main— and pass it in.
What's the main guarantee Abstract Factory gives that separate factory functions don't?
When to use it — and when not to
Use it when code must work with several families of related objects and mixing them would be a bug: UI toolkits per platform, infrastructure per environment, database drivers with matching connection/command/reader classes.
Skip it for one product kind (that's Factory Method or a plain function), or when families never actually exist — one implementation behind an abstract factory is pure ceremony.
Pros and cons
| Pros | Cons |
|---|---|
| Families can't be mixed | Adding a new product kind means editing every factory |
| Swapping families is a one-line change | Many interfaces and classes for the grid |
| Client code is free of concrete classes | Overkill with only one family |
The asymmetry matters: new families are cheap, new product kinds are expensive. If you expect many new kinds of part, this pattern fights you.
How it relates
- Each
create_x()is often a Factory Method. - Concrete factories are frequently single instances (Singleton) or chosen by configuration.
- Builder assembles one complex object step by step; Abstract Factory returns several related objects immediately.
- Bridge pairs well with it: the factory picks the implementation side of the bridge.
You need to add a Cache part to both the cloud and local families. What changes?
Key terms
- Abstract factory — An interface with one creation method per product kind in a family.
- Product family — A set of objects designed to be used together, such as all-cloud or all-local.
- Concrete factory — An implementation that creates every product of one family.