Design Patterns · Creational Patterns
Abstract Factory
Create families of related objects that must match each other, without naming their concrete classes.
This lesson builds on Factory Method. An abstract factory is usually a group of factory methods that belong together.
Abstract Factory gives client code one object that creates a whole family of related products. Every product it returns belongs to the same variant, so the products always fit together. The client never names a concrete product class.
Start from products that must match
A media service stores uploads in blob storage and announces each upload on a message queue. It runs on AWS in production, on Google Cloud for one large customer, and in memory for tests. Two facts define the problem:
- There is a family of related products: a blob store and a queue.
- There are several variants of that family: AWS, GCP, and local.
This service supports one provider per deployment. Its startup configuration pairs storage, messaging, and credentials from that provider. Cross-provider use would need another supported integration. The family rule comes from this service's contract, not from a claim that providers can never interoperate.
Try it
Pick products by hand, or pick a factory
Rows are variants. Columns are products. A factory always hands you one complete row.
store = new S3Store() // in one file
queue = new PubSubQueue() // in another file
→ violates this service's supported deployment family
kit = new AwsKit() // chosen once at startup
kit.createStore() → S3Store
kit.createQueue() → SqsQueue
kit = new GcpKit() // chosen once at startup
kit.createStore() → GcsStore
kit.createQueue() → PubSubQueue
kit = new LocalKit() // chosen once at startup
kit.createStore() → MemoryStore
kit.createQueue() → MemoryQueue
The matrix is also the first implementation step: draw products as columns and variants as rows before writing any code.
Declare the family once, implement it per variant
Abstract Factory takes three moves:
- Declare an interface for each product in the family:
BlobStoreandQueue. Every variant's product implements it. - Declare the abstract factory: an interface with one creation method per product, each returning the product interface.
- Implement one concrete factory per variant.
AwsKitreturns only AWS products.GcpKitreturns only GCP products.
The example shows the factory boundary. Bytes means Uint8Array. The concrete storage and queue classes stand in for provider adapters that implement the two product interfaces. Their SDK code is omitted.
type Bytes = Uint8Array;
interface BlobStore { put(key: string, data: Bytes): Promise<void>; }
interface Queue { send(message: object): Promise<void>; }
interface CloudKit {
createStore(): BlobStore;
createQueue(): Queue;
}
class AwsKit implements CloudKit {
createStore() { return new S3Store(); }
createQueue() { return new SqsQueue(); }
}
class GcpKit implements CloudKit {
createStore() { return new GcsStore(); }
createQueue() { return new PubSubQueue(); }
}
// Client code: only abstract types.
class UploadPipeline {
private store: BlobStore;
private queue: Queue;
constructor(kit: CloudKit) {
this.store = kit.createStore();
this.queue = kit.createQueue();
}
async upload(key: string, data: Bytes) {
await this.store.put(key, data);
await this.queue.send({ type: "uploaded", key });
}
}
If the client works only with abstract types, who creates the concrete factory? Usually the application, once, at startup. It reads configuration or the environment, picks the factory class, and passes the factory to everything that creates products.
const kit: CloudKit =
config.cloud === "aws" ? new AwsKit() :
config.cloud === "gcp" ? new GcpKit() :
new LocalKit();
const pipeline = new UploadPipeline(kit);
Map the roles
| Role | In this example | Job |
|---|---|---|
| Abstract products | BlobStore, Queue | Interfaces for each distinct but related product in the family |
| Concrete products | S3Store, GcsStore, SqsQueue, … | Variant-specific implementations of each abstract product |
| Abstract factory | CloudKit | Declares one creation method per abstract product |
| Concrete factories | AwsKit, GcpKit, LocalKit | Implement every creation method for one variant only |
| Client | UploadPipeline | Works with any factory and product through their interfaces |
Concrete factories instantiate concrete products, but their method signatures return the abstract products. That keeps the client decoupled from the variant it happens to receive.
Check yourself
Which object creates the matching row of products?
Correct. It implements the abstract factory's creation operations for one variant.
Not quite. The client should use product interfaces instead of selecting concrete products independently.
Know which change is cheap
Try it
Two kinds of growth, two very different costs
+ AzureKit implements CloudKit
+ BlobContainerStore implements BlobStore
+ ServiceBusQueue implements Queue
~ startup code: one more branch
client code: unchanged
~ CloudKit: add createSecretStore()
~ AwsKit, GcpKit, LocalKit: implement it
+ SecretStore interface and three implementations
every concrete factory changes
Adding a variant is open/closed. Adding a product type changes the factory interface and every factory. Get the product list right early.
Reach for it when products come in families
- your code must work with several families of related products, and you do not want it to depend on their concrete classes, because the variants are unknown ahead of time or you expect new ones
- a class works with several product types through a group of factory methods, and those methods blur its main responsibility. Move them into a separate factory object.
Check yourself
One object has twelve optional construction steps but no related product family. Which pattern is the closer fit?
Not quite. A count of options does not establish a family of related products.
Correct. The pressure is assembly of a complex object rather than matching several product types.
Implement it in six steps
- Draw a matrix of distinct product types versus variants of those products.
- Declare an abstract product interface for each product type, and make every concrete product implement its interface.
- Declare the abstract factory interface with a creation method for every abstract product.
- Implement one concrete factory class per variant.
- Write initialization code that picks the concrete factory from configuration or the environment, then passes the factory to every class that creates products.
- Find every direct call to a product constructor and replace it with a call to the matching factory method.
Check yourself
In the product matrix, what does one concrete factory represent?
Correct. It creates each product type for that variant.
Not quite. That would select a product type rather than a consistent family.
Name what it costs
| You gain | You pay |
|---|---|
| Products from one factory are guaranteed to match | Many new interfaces and classes, so the code gets more complex |
| Client code is decoupled from concrete products | A new product type changes the factory interface and every concrete factory |
| Product creation lives in one place (single responsibility) | |
| New variants without changing client code (open/closed) |
Check yourself
Adding SecretStore to the family changes which existing abstraction?
Not quite. The factory contract must expose a creation operation before clients can request the product.
Correct. Each factory needs to create the new product type.
Do not confuse it with its neighbors
| Pattern | Relationship |
|---|---|
| Factory Method | Designs often start with Factory Method and evolve here. Abstract factory methods are frequently factory methods, though Prototype can implement them too. |
| Builder | Builder constructs one complex object step by step and lets you run extra steps before you take the result. Abstract Factory returns each product immediately and focuses on families. |
| Facade | When you only need to hide how a subsystem creates its objects, an abstract factory can replace a facade. |
| Bridge | When a bridge's abstractions work only with specific implementations, an abstract factory can encapsulate those pairings and hide them from the client. |
| Singleton | Abstract factories, builders, and prototypes can all be implemented as singletons. One factory per variant is usually enough. |
Say the matrix in an interview
“Products are blob store and queue, variants are AWS, GCP, and local, and a factory per variant guarantees a consistent row.” That one sentence shows you know why the pattern exists. A class diagram alone does not show that.
Check yourself
Can a concrete abstract factory implement one creation operation by cloning a preset?
Correct. Abstract Factory describes family selection. Prototype describes how an individual product can be created.
Not quite. Different patterns can address different pressures in one design.
Retrieve and apply
Check yourself
A UI kit has buttons, checkboxes, and menus in light and high-contrast themes. A screen must never mix themes. Which pattern fits?
Correct. Three product types times two variants is a family matrix. A theme factory guarantees one consistent row.
Not quite. Separate factory methods could still be configured inconsistently. Nothing ties the widgets to one theme.
Not quite. A global theme object does not by itself create matching widgets.
Check yourself
Your abstract factory has four concrete factories. Product managers ask for a new product type. What will the change touch?
Not quite. That is the cost of a new variant, not a new product type.
Correct. A new product needs a new creation method, and every variant must implement it.
Not quite. The client may start using the product, but the factories must learn to create it first.
Continue to Builder for objects that are hard to construct in a single call.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Abstract Factory, printed pages 86–100. Explanations, examples, and exercises are adapted for this course.