The catalog is complete: 23 patterns, three families, and all of PideYa as the demonstration. But real problems don't arrive labeled — no Jira ticket says "apply Strategy here". They arrive as symptoms: a switch that keeps growing, a class nobody wants to touch, a requirement that forces you to change five files. This lesson teaches the method for going from symptom to pattern (or to no pattern at all, which is also a choice): name the problem before the solution, analyze its forces, ask the right diagnostic questions, and validate the answer before writing a single line. It is the first lesson of the craft: the difference between owning the tools and being a good carpenter.
Contents
- Problem first, pattern second
- The catalog of forces: what varies and what must stay stable
- Diagnostic questions by family
- The global decision tree for the GoF catalog
- Three PideYa cases solved with the method
- "Wait until it hurts": the pattern you don't choose up front
- Exercises and conclusion
Problem first, pattern second
The most common mistake when applying patterns is not picking wrong between Strategy and State: it is starting from the pattern. Whoever asks "where can I fit a Visitor?" has already lost — they have a solution in search of a problem. The correct method inverts the order:
- Describe the problem without naming any pattern. In one or two sentences, in the language of the business and the code: "every time marketing adds a coupon type, we touch the checkout class and some case slips through".
- Identify the forces. What changes often? What must remain untouched? Who must not know about whom? (next section).
- Classify the problem by family: is it about creating objects, composing structures, or coordinating behavior?
- Apply the diagnostic questions for that family and narrow down to one or two candidates.
- Validate against the intent and the head-to-head comparison. Read the candidate's "applicability" section and its head-to-head with the neighboring pattern (the comparison lessons exist for this). If the pattern's intent doesn't match your sentence from step 1, start over.
- Run the five filters of the scale: a nameable problem, a real axis of change, present pain, a prepared team, benefit greater than cost.
Notice: the pattern shows up in step 4, not in step 1. Everything before that is problem analysis — and it is the part that sets the professional apart.
The catalog of forces: what varies and what must stay stable
The GoF wrote it in the preface and it is the most useful sentence in the book: "encapsulate what varies". Every pattern protects a stable zone from a specific variable zone. Identifying your problem's axis of variation is identifying half the answer:
| What varies (or you want to be able to vary) | What must stay stable | Family / candidates |
|---|---|---|
| The concrete class being instantiated | The code that uses it | Factory Method, Abstract Factory |
| How a complex object is built | Its final representation and invariants | Builder |
| The starting point of a new object | The process that consumes it | Prototype |
| The interface a third party offers | The interface your code expects | Adapter |
| Two independent dimensions at once | Keeping them from exploding into combined subclasses | Bridge |
| How many extra responsibilities an object carries | Its interface and its core | Decorator |
| The internal structure (leaf or group) | Uniform treatment from the outside | Composite |
| The internal steps of a multi-class process | The client's single call | Facade |
| The algorithm used to do something | The context that invokes it | Strategy |
| What is allowed at each stage of life | The operations the client invokes | State |
| Who reacts to a change | The object that changes | Observer |
| Who handles a request | The sender of the request | Chain of Responsibility |
| The operations over a structure | The classes of the structure | Visitor |
| Concrete steps of a flow | The skeleton of the flow | Template Method |
The table doesn't replace the lessons: it is the reverse index. Your job in front of a real problem is to fill in the first column honestly — and if you can't ("anything could change" is not an answer), you don't yet understand the problem well enough to put a pattern on it.
Two complementary questions sharpen the forces:
- Is the variation in data or in behavior? If the variants only differ in values (a 2.99 fee vs. 4.99), configuration or an enum is enough; patterns pay off when the code differs.
- Who decides the variant, and when? The programmer at compile time (inheritance may be enough), configuration at startup (factories, injection), or the user/state at runtime (Strategy, State)?
Diagnostic questions by family
Before dropping down to concrete patterns, classify the problem. Three triage questions:
- Does the pain show up when writing
new? — who creates, which variant, with how many arguments, at what cost → creational family. - Does the pain show up when drawing the class diagram? — interfaces that don't fit, hierarchies that explode, sprawling subsystems, wildly expensive objects → structural family.
- Does the pain show up when following the flow at runtime? — who calls whom, who finds out about what, where an algorithm lives, what can be undone → behavioral family.
And within each family, the questions you already know from the comparison lessons — condensed here:
| Family | Diagnostic question | If the answer is yes... |
|---|---|---|
| Creational | Does the client know what it wants but not which concrete class? | Factory Method / Abstract Factory |
| Creational | Does construction have many optionals or ordered steps? | Builder |
| Creational | Is creating from scratch expensive, or is the starting point "one like that"? | Prototype |
| Creational | Must there truly be only one... and injecting it won't do? | Singleton (with the critique from 02-02) |
| Structural | Is there correct code with the wrong interface? | Adapter |
| Structural | Is there a part-whole with uniform treatment? | Composite |
| Structural | Do responsibilities need adding without touching the class or subclassing? | Decorator / Proxy (depending on who is in control: head-to-head in 03-09) |
| Behavioral | Must many find out that one changed, without coupling? | Observer |
| Behavioral | Does what is allowed depend on the stage, with transitions? | State |
| Behavioral | Are there alternative algorithms chosen from outside? | Strategy |
| Behavioral | Must requests be queued, audited, or undone? | Command |
The global decision tree
The comparison lessons of each module (creational, structural, behavioral) contain the fine-grained trees per family. This is the top level that drops you at the door of the right tree:
flowchart TD
A[Design problem<br/>named without citing patterns] --> B{Where does it hurt?}
B -- "When creating objects:<br/>who, which, how, how many" --> C[CREATIONAL family]
B -- "In the structure:<br/>interfaces, hierarchies,<br/>composition, cost" --> D[STRUCTURAL family]
B -- "In the behavior:<br/>flow, notifications, algorithms,<br/>state, history" --> E[BEHAVIORAL family]
C --> C1{Does the class, the<br/>construction or the origin vary?}
C1 -- Concrete class --> C2[Factory Method /<br/>Abstract Factory]
C1 -- Complex construction --> C3[Builder]
C1 -- Copy an existing one --> C4[Prototype]
C1 -- Single instance --> C5[Singleton... or injection]
C -.-> CREF[Fine-grained tree: lesson 02-07]
D --> D1{Adapt, compose,<br/>wrap or simplify?}
D1 -- Incompatible interface --> D2[Adapter]
D1 -- "Part-whole / 2 dimensions" --> D3[Composite / Bridge]
D1 -- Wrap with something extra --> D4[Decorator / Proxy]
D1 -- Complex subsystem --> D5[Facade / Flyweight]
D -.-> DREF[Fine-grained tree: lesson 03-09]
E --> E1{Notify, encapsulate a request,<br/>vary behavior or operate<br/>over structures?}
E1 -- Notify / coordinate --> E2[Observer / Mediator]
E1 -- Request as an object --> E3[Command / Chain / Interpreter]
E1 -- Vary behavior --> E4[Strategy / State /<br/>Template Method]
E1 -- Structures and state --> E5[Iterator / Visitor / Memento]
E -.-> EREF[Fine-grained tree: lesson 04-13]
Use it the way you use a compass: it points the way, it doesn't walk you to the door. The final confirmation is always against the pattern's intent and its head-to-head with the confusable neighbor.
Three PideYa cases solved with the method
A creational case: restaurant contracts
The ticket: "When onboarding a restaurant we have to generate its contract. Today there are two types (standard commission and premium flat rate) and legal has announced a third one (pure marketplace) for next quarter. Onboarding today does new CommissionContract(...) or new FlatRateContract(...) based on an if over a string."
- Problem without a pattern: "the onboarding process must generate the right contract without knowing the concrete contract classes, because the types keep growing".
- Forces: what varies is the concrete class being instantiated; what must stay stable is the onboarding flow (validate → create contract → sign → activate). The variation is behavioral (each contract computes settlements differently), not just data.
- Family: the pain is at the
new→ creational. - Diagnosis: does the client know what it wants ("a contract for this restaurant") but not which class? Yes. Whole families of products coordinated by market? No, it's a standalone product. → Factory Method (or its registry-of-
Suppliers variant, like theNotifierRegistryfrom 02-03). - Validation: Factory Method's intent — "define an interface for creating an object, letting subclasses decide which one" — matches. Abstract Factory discarded: there is no family of products that must be consistent with one another.
- Filters: a real axis of change (third type on the roadmap), present pain (the
ifhas already been duplicated in two places). Go ahead.
A structural case: the review aggregator
The ticket: "We are integrating reviews from an external aggregator. Its SDK returns ReviewDTO with getStars() (0-10) and getBody(); our listing-page code expects Review with rating() (0-5) and text()."
- Problem without a pattern: "correct external code, an interface that doesn't fit ours".
- Forces: what varies is the external provider and its interface; what must stay stable is our domain (
Reviewand everything that consumes it). We don't wantReviewDTOleaking through the code. - Family: the pain is one of interface fit → structural.
- Diagnosis: is there correct code with the wrong interface? Yes, literally. → Adapter (
ExternalReviewAdapter implements Review), the same move as thePayPalAdapterfrom 03-02. - Validation: Facade discarded (we are not simplifying a subsystem of our own, we are translating someone else's) and Decorator discarded (we are not adding responsibilities, we are converting an interface).
- Filters: the pain is immediate (without the adapter, the 0-10 → 0-5 conversion would be repeated at every point of use). Go ahead.
A behavioral case: the courier tip
The ticket: "After delivery, the customer can leave a tip. When they do, we have to: credit it to the courier, reflect it in their weekly summary, add it to the zone's metrics and — soon — fire a thank-you notification."
- Problem without a pattern: "a one-off fact (tip recorded) interests a growing number of modules that must not be coupled to the one producing it".
- Forces: what varies is who reacts (three today, four tomorrow); what must stay stable is the recording of the tip. Nobody is directing a protocol: each interested party reacts on its own.
- Family: the pain is in the notification flow → behavioral.
- Diagnosis: must many find out that one changed? Yes. Is there a protocol to direct between them? No. → Observer, reusing the event infrastructure that already broadcasts order transitions (04-08).
- Validation: Mediator discarded with the litmus test from 04-13 — the sender expects no coordination, only that whoever cares finds out.
- Filters: the fourth interested party is already on the roadmap; without Observer, recording the tip would pile up dependencies on accounting, metrics and notifications. Go ahead.
Note the shared rhythm: in all three cases, the pattern was the conclusion of six steps, not the starting point. And in all three, there was an explicit rejection — knowing why it is not the neighbor is worth as much as knowing why it is the chosen one.
"Wait until it hurts"
The method has one more exit, and it is the most frequent one in healthy code: none yet.
Choosing a pattern up front — "promotions are bound to need Interpreter eventually, I'll set it up now" — is a blind bet on the future axis of variation, and the bet almost always loses: the change arrives, but from another direction, and the speculative structure gets in the way instead of helping. Professional discipline is the opposite:
- Write the simple version (the
if, the direct call, the enum) and keep it clean. - Note the intent if you sense the axis of change: a comment saying "if a third contract type appears, extract a Factory" costs nothing and guides the next person.
- Watch for symptoms: the second duplication, the third case in the
switch, the test that can no longer be written. That is the real pain. - When it hurts, refactor toward the pattern — which is cheap precisely because simple code is easy to move. How to do it with a safety net is lesson 05-04.
The rule of three is a good calibrator: the first time you write it inline, the second you duplicate with a guilty conscience, the third you extract the abstraction — because with three cases you finally see the real axis of variation instead of imagining it. Patterns are earned, not planted: it was the golden rule of 01-06, and this module turns it into a method.
Common Mistakes and Tips
- Starting from the pattern ("where can I use an Observer?"). It is the solution in search of a problem. Antidote: force yourself to write the problem sentence without naming patterns; if it won't come out, there is no problem yet.
- Choosing by structural resemblance, not by intent. Strategy and State share a diagram; so do Chain and Decorator. The diagram never decides: the intent decides, and the head-to-head comparisons exist for exactly that.
- Confusing data variation with behavior variation. If the variants only change values, a configuration map beats any pattern.
- Settling for the first candidate. Always force an explicit rejection ("it's X and not Y because..."). If you can't articulate the rejection, you haven't finished the diagnosis.
- Forgetting the "none" option. The global tree has an invisible branch coming out of the root: "the simple code doesn't hurt yet → wait". It is the right branch more often than enthusiasm admits.
- Tip: put a date on your waits. "Not today; revisit when marketing launches the third coupon type" turns prudence into a recorded decision, not an oversight.
Exercises
Exercise 1: from ticket to diagnosis
Apply the six steps of the method to this PideYa ticket: "Premium restaurants want to customize the ticket printed in the kitchen: some add their logo, others a message from the chef, others the allergen breakdown — and any combination of the three. Today there is one subclass of KitchenTicket per combination and we're already at five". Name the forces, the family, the candidate, and the pattern you rejected.
Exercise 2: pattern or wait?
For each situation, decide concrete pattern or wait (with the simple version you would leave in place), justifying with the forces:
- "The commission calculation applies a different percentage per restaurant type; there are three types, stable for two years, and they only differ in the number."
- "When an order is confirmed, the kitchen must be notified. Only the kitchen. Nothing else is planned."
- "The order status today is an enum with a
switchin four methods; support asks to add the WAITING_FOR_RESTAURANT status with its own cancellation rules, and it's the second new status this year."
Exercise 3: reconstructing the diagnosis
A colleague proposes: "for discount coupons, let's build an Abstract Factory with one factory per coupon type". The coupons are: a percentage off the total, a fixed amount, and free delivery — a single object each, with no coordinated families. Write (a) which diagnostic question failed in their analysis, (b) which candidate comes out of the method properly applied, (c) how you would explain it to them without undermining them.
Solutions
Solution 1: (1) Problem: "add optional, combinable extras to the ticket without one subclass per combination". (2) Forces: what varies is the add-ons and their combinations; stable is the base ticket and its interface. (3) Family: structural — the pain is the hierarchy explosion. (4) Diagnosis: adding responsibilities without touching the class, composable at runtime? → Decorator, the same move as the dish extras in 03-05. (5) Rejection: Template Method would fix the variants through inheritance and reproduce the combinatorial explosion; Strategy would vary the whole printing algorithm, but here the pieces stack, they don't replace each other. (6) Filters: five subclasses already hurt — go ahead.
Solution 2: (1) Wait: the variation is in data (a percentage), not in behavior — a Map<RestaurantType, BigDecimal> or the enum itself with a field solves it; Strategy would be an empty wrapper. (2) Wait: a single receiver with no more in sight — a direct call with the dependency injected (DIP, testable), and an intent note saying "if more interested parties appear, Observer". Building Observer for one observer is the welcome-email cathedral. (3) Pattern: State — second new status in a year (a real, recurring axis of change), per-status rules, a switch repeated in four methods (present pain). The diagnosis and the mechanics are in 04-09; how to migrate the switch without breaking anything, in 05-04.
Solution 3: (a) The question that failed was "are there families of products that must be consistent with one another?" — Abstract Factory pays off when several coordinated products are created (the MarketFactory created gateway + taxes + receipt); here each coupon is a standalone product. (b) The real forces: what varies is the discount algorithm, stable is the checkout that applies it → Strategy (a DiscountRule with three implementations), and at most a simple Factory Method if creating from the coupon code calls for it. (c) By acknowledging what their instinct got right ("you saw correctly that creation doesn't belong in the checkout") and redirecting with the question, not the conclusion: "what family of coordinated objects does each factory create?" — the right question lets your colleague reach the rejection on their own.
Conclusion
You now have the method: a problem named without patterns, the forces on the table (what varies, what stays stable), triage by family, diagnostic questions, validation against the intent with an explicit rejection, and the filters of the scale as the final check — with "wait until it hurts" as a legitimate and frequent outcome. It is the same path in every family, and the three PideYa cases prove it step by step. But the method has been trained here on problems with a single answer; real code weaves several patterns into one feature, and there the decisions condition one another. Seeing the method work at scale — the complete flow of an order, and a new module built from scratch choosing each piece — is the next lesson: Practical Examples of Pattern Usage.
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
