Sometimes you don't need one object, you need a set of objects that must match: dark-theme buttons and dark-theme modals, or AWS compute and AWS storage. Abstract Factory gives you one factory per family, so everything you get from it belongs together.
Intent#
Provide an interface for creating families of related or dependent objects without specifying their concrete classes. (GoF)
The easiest way to tell it apart from Factory Method is to count what you create:
- Factory Method: one product from a single hierarchy. "Give me a
Payment": credit card or PayPal. - Abstract Factory: a family of related products designed to work together. "Give me the AWS suite": compute and storage.
The problem: mix-and-match bugs#
Your app provisions a compute instance and a storage bucket. With two independent factories, nothing stops someone from mixing providers:
compute = ComputeFactory.create("AWS") # AWS EC2
storage = StorageFactory.create("GCP") # Google Cloud Storage
# Bug: an AWS server talking to GCP storage means cross-cloud latency,
# complicated networking and mismatched IAM permissions.
Structure#
The pattern has five participants:
| Participant | Role |
|---|---|
| Abstract products | One interface per kind of product (Compute, Storage) |
| Concrete products | One implementation per family (EC2Instance, ComputeEngine, ...) |
| Abstract factory | One creation method per product kind (create_compute, create_storage) |
| Concrete factories | One per family (AWSFactory, GCPFactory); each returns only its own family |
| Client | Receives one factory and uses only the abstract interfaces |
Example: cloud provisioning#
Here each cloud provider is a family, and each factory returns only that provider's compute and storage.
from abc import ABC, abstractmethod
# 1. Abstract products
class Compute(ABC):
@abstractmethod
def start(self) -> str: ...
class Storage(ABC):
@abstractmethod
def save(self, data: str) -> str: ...
# 2. Concrete products, grouped by family
class EC2Instance(Compute):
def start(self) -> str:
return "AWS EC2 instance started."
class S3Bucket(Storage):
def save(self, data: str) -> str:
return f"Saved '{data}' to AWS S3."
class ComputeEngine(Compute):
def start(self) -> str:
return "GCP Compute Engine started."
class GoogleCloudStorage(Storage):
def save(self, data: str) -> str:
return f"Saved '{data}' to Google Cloud Storage."
# 3. Abstract factory: the contract for a whole family
class CloudFactory(ABC):
@abstractmethod
def create_compute(self) -> Compute: ...
@abstractmethod
def create_storage(self) -> Storage: ...
# 4. Concrete factories: each one only knows its own family
class AWSFactory(CloudFactory):
def create_compute(self) -> Compute:
return EC2Instance()
def create_storage(self) -> Storage:
return S3Bucket()
class GCPFactory(CloudFactory):
def create_compute(self) -> Compute:
return ComputeEngine()
def create_storage(self) -> Storage:
return GoogleCloudStorage()
# 5. Client: takes ONE factory, so both resources come from
# the same provider
class InfrastructureProvisioner:
def __init__(self, factory: CloudFactory):
self.factory = factory
def deploy_stack(self) -> None:
server = self.factory.create_compute()
bucket = self.factory.create_storage()
print(server.start())
print(bucket.save("production backup"))
InfrastructureProvisioner(AWSFactory()).deploy_stack()
# Swap the whole family:
InfrastructureProvisioner(GCPFactory()).deploy_stack()
Because the client gets a single factory, it can't accidentally combine AWS compute with GCP storage. Switching providers is a one-line change where the factory is chosen.
When to use it#
Reach for Abstract Factory when these conditions hold:
- Your system must work with several families of related products (UI themes, cloud providers, database vendors, OS-specific widgets).
- Products in a family must be used together, and mixing them would be a bug.
- You want to swap an entire family at once, usually from configuration.
The wording of a problem usually tells you which factory pattern fits:
| Prompt signal | Pattern |
|---|---|
| "Support multiple payment gateways (Stripe, PayPal)" | Factory Method: one product |
| "Support light and dark themes for buttons and modals" | Abstract Factory: a family |
| "Deploy to AWS or GCP: servers and databases" | Abstract Factory: a family |
When not to use it#
Skip it when there is no real family to protect:
- You only create one kind of product. Use Factory Method or a simple factory.
- There's only one family and no realistic second one. You'd add interfaces for nothing.
Pitfalls#
Watch for these costs:
- Adding a new product kind is expensive. Adding
create_queue()means changing the abstract factory and every concrete factory. Adding a new family, by contrast, is cheap (one new factory class). - Class count grows fast: products × families, plus one factory per family.
- Concrete factories are often singletons (one
AWSFactoryper app is enough), but you don't need to enforce that.
Key takeaways#
- Abstract Factory creates a family of related objects; Factory Method creates one.
- One concrete factory per family guarantees consistency: you can't mix families.
- Clients depend only on abstract products and the abstract factory.
- New families are easy to add; new product kinds touch every factory.