You now know the five creational patterns individually. This final lesson of the module puts them on the same table, which is where real decisions get made: the question is rarely "how does Builder work?" but "what do I use here: a builder, a factory, or nothing?". We will compare intents and costs, give you a decision guide in diagram form, see how they combine with one another (in real code they almost never appear alone), and trace their typical evolution over a project's life. We will close with PideYa's complete creational map: which pattern ended up installed in each part of the system and why.
Contents
- The five, face to face
- Decision guide
- How they combine with one another
- The typical evolution: patterns are earned
- PideYa's creational map
- Common mistakes, exercises, and conclusion
The five, face to face
The table worth internalizing (the cost columns pick up the scale from lesson 01-06):
| Pattern | Intent in one sentence | Use it when... | Main cost |
|---|---|---|---|
| Singleton | A single instance, with global access | A technical resource must be unique and shared (and there is no DI container to manage it) | Global state: hidden dependencies, brittle tests; injecting is almost always better |
| Factory Method | A method decides which concrete class to instantiate; variants override it or inject it | The product's class varies and you want to add variants without touching the code that uses them | One creator/product pair per variant: a hierarchy or registry to maintain |
| Abstract Factory | An object manufactures a consistent family of products | Several interchangeable families whose pieces must not mix (PideYa's markets) | A matrix of classes (variants × products); adding a product breaks every factory |
| Builder | Build a complex object step by step, validating at the end | Many parameters/optionals, cross-field validation, immutability wanted | One builder class per product; excessive ceremony for small objects |
| Prototype | Create by copying an existing exemplar | The natural starting point is an already-configured object (repeat, templates) | Reasoning out the copy policy (shallow/deep) field by field |
Two cross-cutting axes that help keep them apart:
- Where is the difficulty? In choosing the class → factories (one piece: Factory Method; a consistent family: Abstract Factory). In assembling the object → Builder. In the starting point (an equal one already exists) → Prototype. In how many instances → Singleton.
- What do you feed the mechanism? A factory gets parameters and decides the class; a builder gets data bit by bit and assembles; a prototype gets nothing: it already contains its state.
Decision guide
The question flow that sums up the module. Like every guide, it steers 90% of cases; the remaining 10% is judgment (and remember the filter that precedes them all: does the direct new actually hurt?):
flowchart TD
A[I need to create an object] --> B{Does the direct 'new'<br/>already hurt?<br/>symptoms from 02-01}
B -- No --> Z[direct new or simple factory<br/>and move on: YAGNI]
B -- Yes --> C{Is the problem<br/>HOW MANY instances?}
C -- "There must be only one" --> D[Is there a DI container?]
D -- Yes --> D1[Container's<br/>singleton scope]
D -- No --> D2[Singleton<br/>holder idiom or enum]
C -- No --> E{Does an exemplar already exist<br/>that is the best blueprint?}
E -- Yes --> F[Prototype<br/>+ registry if there is a catalog]
E -- No --> G{Is the difficulty in<br/>the ASSEMBLY?<br/>many optionals, validation}
G -- Yes --> H[Builder<br/>fluent variant]
G -- No --> I{Several pieces that must<br/>be CONSISTENT with one another?}
I -- Yes --> J[Abstract Factory]
I -- No --> K[Factory Method<br/>or a Supplier registry]
Three tips for using the guide:
- The answers can be several at once: a complex order (Builder) whose payment gateway is chosen by a market factory (Abstract Factory) is the norm, not the exception. The guide applies per creation decision, not per system.
- When torn between Factory Method and Abstract Factory, the discriminating question is always the same: is there a consistency invariant across several products? No family, no Abstract Factory.
- When torn between Builder and a factory: the factory hides which class; the builder hides which steps. If the client knows the class perfectly (
Order) but suffers assembling it, it is Builder.
How they combine with one another
The creational patterns are Lego pieces, and in real code they show up assembled. The canonical combinations, all present or plausible in PideYa:
- Abstract Factory implemented with Factory Methods. Each
createX()method ofSpainFactoryis a factory method: the abstract factory is, structurally, a bundle of factory methods grouped by variant. You saw it in lesson 02-04. - Factories exposed as a Singleton (or better, injected as a single instance).
MexicoFactoryhas no state of its own: there is no reason to instantiate it more than once. The usual arrangement: a single instance created at the composition root and injected; the classic Singleton remains for the container-less case. - A Builder inside a factory. A factory whose product is complex assembles it internally with its builder:
SpainFactory.createReceiptFormatter()may returnReceiptFormatter.builder().regulation(ES).currency(EUR).build(). The client sees a factory; the factory uses a builder. Each pattern solves its own layer. - A factory that returns the builder.
Order.builder(customer, restaurant)— our entry point from lesson 02-05 — is a static creation method... that manufactures a builder. The combination is so natural it goes unnoticed. - Abstract Factory implemented with Prototype. Instead of one factory class per market, a set of prototypical exemplars per market that the factory copies. Useful when variants are defined by configuration (data) rather than by code.
- A registry (of Suppliers or of prototypes) as the backbone. The registry from lesson 02-03 and the template registry from lesson 02-06 are the same skeleton with different filling: recipes in one, exemplars in the other. Both turn "adding a variant" into "registering an entry", without touching the core.
The typical evolution: patterns are earned
The sequence you saw in the lessons is no accident: it is the natural trajectory of a creation point over the life of a healthy project. It pays to make it explicit, because it tells you when to stop:
flowchart LR
A[direct new] -->|"2nd concrete class<br/>or duplicated switch"| B[Simple factory<br/>idiom, not a pattern]
B -->|"extension without touching<br/>the core: real OCP"| C[Factory Method<br/>or a Supplier registry]
C -->|"a 2nd piece appears that<br/>must be consistent with the 1st"| D[Abstract Factory]
Each arrow has an entry toll: the symptom that justifies it. Walking the whole road "just in case" on day one is the over-engineering of lesson 01-06; staying at direct new when there are already three duplicated switch blocks is the symmetric mistake. PideYa walked the full sequence only for payments and taxes (where internationalization demanded it); notifications stopped at the Supplier registry; and dozens of small objects happily remain at direct new. All three stops are correct: each is the proportionate answer to its level of pain. Builder and Prototype orbit outside this sequence: they do not evolve from the factory but from the object (a swelling constructor → Builder; repetitive rebuilding → Prototype). And the reverse road exists too: if an Abstract Factory's variants shrink to one and nothing suggests they will return, downgrading it to a simple factory is good refactoring, not surrender (more on this in Refactoring with Design Patterns).
PideYa's creational map
Let's close the module the way it began: looking at the whole system. This is what ended up installed, lesson by lesson, with its one-line justification:
| Part of PideYa | Creational solution | Why that one and not another |
|---|---|---|
Global configuration (PideYaConfig) |
Single instance injected from the composition root (the classic Singleton stayed as a teaching piece) | Uniqueness is a deployment decision; injecting keeps dependencies visible and tests clean |
Event log (EventLog) |
Enum singleton | Technical, cross-cutting resource with no business state: the pattern's benign case |
| Notifiers (push/SMS/email/WhatsApp) | Factory Method in its modern form: a registry of Supplier<Notifier> |
One product per channel, frequent extension, no family invariant |
| Payments + taxes + receipts per market | Abstract Factory (MarketFactory: SpainFactory, MexicoFactory...) |
Three pieces with a per-country consistency invariant: mixing must be impossible by types |
Creating an Order |
Fluent Builder (Order.builder(...)) with validation in build() and immutability |
Many optionals and cross-field rules; the class was known, the assembly was the problem |
| Recipes for frequent orders | Modern Director (OrderRecipes) on top of the builder |
Repeated combinations deserve a name, not full GoF ceremony |
| "Repeat my last order" | Prototype via copy constructor (Order.copy()) |
The best blueprint for the new order is the old order; the deep copy of lines keeps the history uncorrupted |
| Menu templates by cuisine type | Prototype with a prototype registry (MenuTemplateRegistry) |
A catalog of exemplars expensive to assemble, extensible with data without deploying code |
Small, stable objects (Address, OrderLine, Money...) |
Direct new or records |
No symptom: any pattern here would be noise |
Note the last row: it is as important as the rest. A mature design is not the one with the most patterns, but the one with each pattern where its symptom justified it — the yardstick we set in the lesson on the scale.
Common Mistakes and Tips
- Choosing by familiarity, not by problem. "I use Factory Method because it's the one I know best" produces factories where a builder was needed. The decision guide exists to force the right questions before the comfortable answers.
- Confusing the star pair. Factory Method and Abstract Factory will keep getting mixed up in interviews and reviews; recite the discriminator: a method that creates one product versus an object that creates a consistent family.
- Skipping rungs of the evolution. Setting up the market Abstract Factory when PideYa operated only in Spain would have been pure speculative flexibility. Each arrow's toll is paid when the symptom arrives, not before.
- Not dismantling patterns that no longer pay. The evolution also runs backwards: keeping a factory hierarchy for a single variant two years running is structural debt dressed up as design.
- Treating the combinations as exceptions. A builder inside a factory, or a factory that returns builders, is not "mixing patterns": it is the norm. Each pattern governs one layer of the creation decision.
- Tip: on your next project, do the map exercise: a table like PideYa's with every relevant creation point, its solution, and its one-line justification. If any row cannot be justified with a symptom, you have a candidate for simplification.
Exercises
Exercise 1: express diagnosis
For each new situation in PideYa, pick the creational pattern (or the absence of one) and justify it in one sentence using the right discriminator:
- The reporting module must generate a
StatisticsPanelobject with 12 configurable widgets, optional thresholds, and range validation. - Marketing wants email campaigns whose content is defined visually in an admin panel; each new campaign starts from an existing one "that worked".
- When integrating in-store pickup for supermarkets, each chain (Mercadona, Carrefour) demands its own stock API, its barcode format, and its loyalty gateway, always matching.
- A developer proposes making the
CurrentCarta singleton "to access it from any screen of the app". - The right
DataExporterobject (CSV, JSON, or Parquet) must be created based on a parameter; today there are three formats and no families or consistency requirements are expected.
Exercise 2: spot the combination
Describe which creational patterns collaborate in this fragment and what role each one plays:
public class MexicoFactory implements MarketFactory {
@Override
public ReceiptFormatter createReceiptFormatter() {
return ReceiptFormatter.builder()
.regulation(Regulation.MX)
.currency(Currency.MXN)
.legend("Este ticket no es un CFDI")
.build();
}
// ...
}Exercise 3: the evolution of a creation point
PideYa's promo code generator was born as new CodeGenerator() in a single service. Today: (a) marketing wants alphanumeric codes for regular campaigns and QR codes for billboard campaigns; (b) the switch distinguishing them is already copied into two services. Describe the step-by-step evolution that applies the section 4 sequence, indicating at which stop you would halt today and which future symptom would push you to the next one.
Solutions
Solution 1:
- Builder: the class is known and the problem is the assembly (optionals + cross-field validation).
- Prototype with a registry: the exemplars are defined with data at runtime and the natural starting point is an existing campaign; no solution based on new classes fits an admin panel.
- Abstract Factory: three pieces per chain with a consistency invariant ("always matching"): a textbook family.
- No pattern, plus a reasoned rejection: the cart is mutable, per-user business state; a singleton would share it across sessions (a serious bug) and hide the dependency. It belongs to the session/context, injected.
- Simple factory (or a
Supplierregistry if more formats are expected): one product, no families; with three stable cases, the idiom is enough and the full pattern would be ceremony.
Solution 2: the collaborators are Abstract Factory (the MexicoFactory class, which guarantees the Mexican market's consistent family), Factory Method (the createReceiptFormatter() method is one of its factory methods: it decides this variant's concrete product), and Builder (ReceiptFormatter.builder()...build() assembles the complex product step by step, with whatever validation belongs in build()). Three layers of the same decision: which family → which product → how it is assembled.
Solution 3: a reasonable evolution: (1) the direct new stopped being enough when the second concrete class appeared (alphanumeric/QR) and the switch got duplicated: clear symptoms → extract a simple factory (CodeGeneratorFactory.create(type)) that deduplicates and centralizes. (2) Current stop: that is where I would halt today — two stable variants and a single decision point justify no more. (3) The symptom that would push toward Factory Method/a Supplier registry: new variants arriving at a real cadence (codes per partner, per external app) or the need for other modules to register generators without touching the core. (4) Abstract Factory would only enter if some day each campaign demanded several consistent pieces (generator + validator + renderer, matching per campaign type); that family does not exist today, so anticipating it would be speculation.
Conclusion
The module opened with a question — who decides which concrete class gets instantiated, and where? — and you now have five answers with their discriminators: Singleton for uniqueness (better: injection), Factory Method to vary the product, Abstract Factory to lock in families, Builder to tame assembly, Prototype to start from an exemplar. You know they combine in layers, that you arrive at them through a symptom-driven evolution — and leave them the same way — and you have PideYa's map as a template of the end result: each pattern where its pain justified it, and direct new where there was no pain.
With the birth of objects solved, the next question is inevitable: once the pieces are created — gateways, notifiers, orders — how do they connect and organize without the whole turning into a tangle? That is the catalog's second family: adapting interfaces that do not fit, composing tree structures, wrapping objects with extra responsibilities, simplifying entire subsystems behind a facade. See you in the Introduction to Structural Patterns.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What Are Design Patterns?
- History and Origin of Design Patterns
- Design Principles: SOLID and Other Foundations
- Essential UML for Understanding Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
- Introduction to Creational Patterns
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparing and Choosing Creational Patterns
Module 3: Structural Patterns
- Introduction to Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparing and Choosing Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparing and Choosing Behavioral Patterns
Module 5: Applying Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring with Design Patterns
- Anti-Patterns: When Patterns Become a Problem
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Concurrency Patterns
- Design Patterns in Agile Development
