Free Interactive Course · Design Patterns

Abstract Factory Design Pattern

Make whole families of objects that are guaranteed to belong together — no accidental mixing.

Creational Patternsmediuma.k.a. Kit
ShareXLinkedIn
In one sentence

Provide an interface for creating families of related objects without specifying their concrete classes.

01

The problem Abstract Factory solves

The problem

Your service needs three infrastructure pieces: object storage, a message queue, and a secrets store. On AWS that's S3, SQS and Secrets Manager. In local development it's MinIO, an in-memory queue, and a .env file.

So each one gets its own factory, and the wiring code picks three implementations independently. Which works right up until someone configures S3Storage with InMemoryQueue — a combination that compiles, starts up, passes the smoke test, and then loses every message on restart in staging.

The individual choices were all valid. The combination wasn't, and nothing in the code could say so.

02

How the Abstract Factory pattern works

Stop choosing the pieces separately. Choose the family once, and let it hand you every member:

  1. Define an abstract factory with one creation method per product: createStorage(), createQueue(), createSecrets().
  2. Each concrete factory implements all of them with one consistent family — AwsFactory returns S3, SQS and Secrets Manager and nothing else.
  3. The application picks a factory exactly once, at startup, and asks it for everything. Mixing families becomes unrepresentable.
The difference from Factory Method, precisely. Factory Method makes one product and varies it by inheritance — a subclass overrides the creation method. Abstract Factory makes a family and varies it by composition — you hold a factory object and ask it for each member. In practice an Abstract Factory is usually implemented as several Factory Methods on one interface, which is why the two get conflated.
«interface» InfraFactory+ createStorage() : Storage+ createQueue() : Queue+ createSecrets() : SecretsAwsFactoryLocalFactoryS3Storage · SqsQueueSecretsManagerSecretsMinioStorage · MemQueueDotEnvSecretsone choice,whole family
Participants. The Abstract Factory declares one creation method per product type. Each Concrete Factory supplies one internally-consistent family. Abstract Products are the interfaces the application codes against. The client holds a factory, never a concrete product class — so the colours can never mix.
03

See it: one choice, a whole matched family

Pick an environment. Each factory hands back all three pieces at once, from one family — there is no path through this that produces S3 plus an in-memory queue.

▶ Try it — pick a family, not a piece

An interactive selector: choosing an environment selects one concrete factory, which supplies a matched set of storage, queue and secrets implementations — mixing implementations across families is not possible.

Press Add a new environment to bring GCP online. Adding a whole cloud is one new class implementing three methods — and the application code that uses storage, queues and secrets is untouched. That's the payoff, and it's also the cost: see the pitfall below about adding a fourth product.
04

Abstract Factory code examples

One factory per environment, each supplying a consistent infrastructure family.

public interface Storage { void put(String key, byte[] data); }
public interface Queue   { void publish(Message m); }
public interface Secrets { String get(String name); }

// The abstract factory: one creation method per product in the family.
public interface InfraFactory {
    Storage createStorage();
    Queue   createQueue();
    Secrets createSecrets();
}

public final class AwsFactory implements InfraFactory {
    public Storage createStorage() { return new S3Storage(region); }
    public Queue   createQueue()   { return new SqsQueue(region); }
    public Secrets createSecrets() { return new SecretsManagerSecrets(region); }
}

public final class LocalFactory implements InfraFactory {
    public Storage createStorage() { return new MinioStorage("http://localhost:9000"); }
    public Queue   createQueue()   { return new InMemoryQueue(); }
    public Secrets createSecrets() { return new DotEnvSecrets(Path.of(".env")); }
}

// Chosen ONCE, at the edge of the application:
InfraFactory infra = switch (env) {
    case "prod"  -> new AwsFactory();
    case "local" -> new LocalFactory();
    default      -> throw new IllegalArgumentException(env);
};

var app = new App(infra.createStorage(), infra.createQueue(), infra.createSecrets());

// App only ever sees Storage, Queue and Secrets. There is no expression
// anywhere in the codebase that yields S3Storage next to an InMemoryQueue.
Read across the tabs: Python and Go both point out that a plain bundle — a NamedTuple or a struct of interfaces, built by one function per family — gives the identical guarantee with far less machinery. The full factory interface earns its place when the family must be swapped at runtime, not merely chosen at startup. The C# tab shows the trap worth remembering in any DI codebase: registering the three products separately quietly throws the family guarantee away.
05

How to implement Abstract Factory

  1. Write down the matrix: products across the top, families down the side. If it isn't a full grid — some families genuinely lack some products — this pattern will fight you.
  2. Define an abstract product interface per column.
  3. Define the abstract factory with one creation method per column.
  4. Implement one concrete factory per row, returning only that family's classes.
  5. Choose the factory exactly once, at the composition root. Every additional selection site is another chance to mix families.
  6. Make sure the application only ever names the abstract products. A single new S3Storage() deep in the code defeats the whole thing.
06

When to use Abstract Factory — and when not to

Use it when several objects must come from the same family to be correct, when you support multiple backends or platforms, or when tests need a wholesale swap to fakes. The signal is a combination that compiles but is wrong.

Where it goes wrong

Adding a product is expensive. Adding a family is cheap — one new class. Adding a fourth product means changing the factory interface and every concrete factory. If your products change more often than your families, you have the matrix the wrong way round and this pattern is actively costly.

The family that isn't square. When the local family has no secrets manager, you get a stub throwing UnsupportedOperation — a hole in the abstraction that shows up at runtime, in production, on the one path nobody tested.

Applied at one family. An abstract factory with a single implementation is three interfaces and a class doing what a constructor already did. Wait for the second backend.

Leaky concrete types. The guarantee only holds while the application code speaks in abstract products. One cast to S3Storage to reach a bucket-specific method and you're coupled to AWS again.

You have…Reach forWhy
One product whose class variesFactory MethodAbstract Factory's machinery buys nothing for a single product.
Several products that must be consistent with each otherAbstract FactoryThe family is the unit of choice, so mismatches become unrepresentable.
Products changing far more often than familiesA registry or DI containerEvery new product edits every factory. That trade only pays off in reverse.
One complex product with many optional partsBuilderThat's an assembly problem, not a family problem.
Families already expressed as configOne constructor per familyA struct or NamedTuple bundle gives the same guarantee with less code.
07

Quick check

🧠 Quick check
You have InfraFactory with three creation methods and four concrete factories. Now you need a fourth product, a cache. What does that cost?

In the wild

JavaDocumentBuilderFactory and TransformerFactory in JAXP — pick a factory, get a matched parser toolchain.
C#DbProviderFactory — one factory yields a matching connection, command and parameter set, so you can't pair a SQL Server command with a Postgres connection.
Pythonmultiprocessing.get_context("spawn") returns a context whose Queue, Lock and Process all work together — an abstract factory in the standard library.
Gocrypto/tls cipher-suite selection, and the common "one constructor per environment returning a struct of interfaces" wiring idiom.
C++Qt's QStyleFactory, producing a widget style whose components are consistent with each other.
TypeScriptTesting setups that swap an entire adapter family — real HTTP, storage and clock in production, fakes in tests — behind one factory.

Frequently asked questions

What is the difference between Abstract Factory and Factory Method?
Factory Method creates one product and varies it through inheritance: a subclass overrides the creation method. Abstract Factory creates a family of related products and varies it through composition: you hold a factory object and ask it for each member. An Abstract Factory is typically implemented as several Factory Methods on one interface, which is why they're so often confused. Ask yourself whether your problem is "which class do I instantiate" (Factory Method) or "these objects must all come from the same set" (Abstract Factory).
When is Abstract Factory the wrong choice?
When products change more often than families. Adding a family costs one class; adding a product costs a change to the interface and to every concrete factory. If you're constantly adding product types across a stable set of two backends, that trade is backwards and a registry or DI container will serve you better. It's also wrong when the matrix isn't full — a family missing a product forces stub implementations that throw at runtime.
Does a DI container replace Abstract Factory?
Partly, and you should be careful about the part it doesn't. A container removes the coupling to concrete classes, which is most of the benefit. But registering each interface separately loses the family guarantee — nothing stops a configuration that pairs real S3 storage with an in-memory queue. Registering the factory itself, and resolving the products from it, keeps both.
Is Abstract Factory only for cross-platform UI toolkits?
That's the original GoF example, and it's the least relevant one today. The pattern is alive and well anywhere a set of collaborating implementations must agree: cloud provider abstractions, database driver families, real-versus-fake infrastructure in tests, and multi-tenant deployments where a tenant's storage, queue and identity provider are chosen together.

Finish the handbook, earn the certificate

All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.

See all 23 patterns →
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.