Design Patterns Deep Dive · 1 of 4

Creational Patterns — Controlling How Objects Are Born

Creational patterns answer one question: who's allowed to call new, and how? They centralize construction so the rest of the code stays free of "which concrete class do I instantiate here?" decisions. Some are timeless; one (Singleton) has aged into an anti-pattern in modern code.

FactoryBuilderSingletonPrototypeAbstract Factory
← Back to Architecture
Quick Facts

What Creational Patterns Solve

Basic Concepts

  • The problem: new ConcreteClass() hard-codes a dependency. Callers can't substitute, can't test, can't extend.
  • The solution: hide construction behind a method or another object. Callers ask for "a logger" or "an order with these fields"; something else picks the concrete type.
  • The trade-off: indirection. One more layer between "I want one" and "here it is."
  • The modern context: DI containers and language features (named arguments, default values, builders generated by macros) cover most of what these patterns were invented for.
The Patterns

Five Patterns, One Family

Factory Method

Intent: Define an interface for creating an object, but let subclasses (or configuration) decide which concrete class to instantiate.

A method returns an object whose concrete type can vary. Callers depend on the abstract return type; the factory picks the implementation. Common in plugin systems, platform-specific code, and "give me a logger / cache / parser based on this name."

Example: Logger.create("file") returns a FileLogger; Logger.create("syslog") returns a SyslogLogger; both expose the same Logger interface.

When to use: the caller cares about the abstraction, not the concrete class. The choice depends on runtime config, OS, or content type.

Abstract Factory

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

A factory of factories. WindowsUIFactory creates Windows-styled buttons, scrollbars, menus; MacUIFactory creates Mac-styled ones. Switch the factory, switch the whole skin coherently.

Modern echoes: cloud SDKs use this — AWSClientFactory hands you S3, SQS, DynamoDB clients all configured against the same region and credentials.

Watch out: easy to over-apply. If the family is "one thing," it's just a Factory Method.

Builder

Intent: Construct a complex object step by step, separate from its representation, so the same construction process can produce different results.

Especially nice for immutable objects with many optional fields. Avoids constructor explosion (12 nullable parameters in some specific order). The Builder collects parameters fluently and produces the final object on build().

HttpRequest req = HttpRequest.builder()
    .url("https://api.example.com/orders")
    .header("Authorization", token)
    .timeout(Duration.ofSeconds(5))
    .body(payload)
    .build();

Modern alternatives: named/default arguments (Python, Kotlin, Swift, C#) cover most Builder use cases without the boilerplate. Java/older C++ still benefit from explicit Builders. Lombok's @Builder generates them.

Prototype

Intent: Create new objects by cloning an existing prototype rather than instantiating from scratch.

Useful when construction is expensive (loaded a 50 MB model? clone, don't reload), or when the prototype carries pre-configured state you want to start from. JavaScript's whole object model is built on prototypes; in classical OO languages, you implement clone().

Watch out: shallow vs deep clone. Cloning an object that holds a reference to a mutable collection without deep-copying the collection is a classic source of nasty aliasing bugs.

Singleton — Use With Care

Intent: Ensure exactly one instance of a class exists, and provide a global point of access.

Sounds tidy. In practice it's hidden global state with predictable consequences: untestable code (you can't substitute the singleton in a test), hidden coupling (callers reach into a static accessor), lifecycle issues (when does it start? when does it stop? what about thread safety?).

Modern advice: if you need exactly one of something, register it as a singleton in your DI container and inject it where it's needed. Don't expose a static accessor. The pattern's "global access" promise is precisely what makes it harmful.

Acceptable uses: truly process-wide concerns where injection is impractical — a logger root, a metrics registry. Even then, prefer DI when you can.

Object Pool (Honorable Mention)

Not in the original GoF, but creational in spirit. Pre-create a pool of expensive objects (DB connections, thread workers, large buffers) and check them in/out instead of creating fresh. Used by every connection pool, thread pool, and buffer pool you've ever touched.

Modern Reality

Where DI Containers Replaced These

Most of the original creational-pattern motivation — "decouple callers from the concrete class they instantiate" — is now solved by Dependency Injection at the application level. Spring, ASP.NET DI, NestJS, Guice, Dagger all let you wire concrete implementations once, in a composition root, and inject them through constructors.

You still see Factory, Abstract Factory, and Builder in:

  • Library APIs that have to work without a DI container.
  • Cases where the concrete type depends on runtime data, not config — a parser keyed on the file's first bytes.
  • Immutable value objects with many fields where Builder remains a real win.
Continue

Other Pattern Families