A complete guide to all 23 patterns, practical examples, SOLID, and real-world architecture
You will not memorize 23 names: you will learn to recognize the design problems that make a pattern useful.
By João Ricardo Dutra••Complete material
What this guide promises
You will not memorize 23 names. You will learn to recognize the recurring design problems that make a pattern useful - and the situations in which a pattern would only make the code worse.
Expanded English edition based on the supplied GoF/Java manuscript.
Editorial Note
This article uses the supplied Portuguese manuscript as its editorial backbone and expands it in English with additional general software-engineering context. The original structure - history, the 23 Gang of Four patterns, frequently encountered Java patterns, deep dives, SOLID relationships, framework examples, overengineering warnings, mental models, and decision guides - has been preserved in spirit while the prose has been rewritten for a broad international audience.
Reader mindset
Whenever possible, look at the problem before the pattern. Ask what is changing, what is coupled, what is duplicated, and what should remain stable. The name of the pattern should come second.
Table of Contents
Introduction: Why Design Matters More Than Working Code
From Architecture to Software: The History Behind Design Patterns
What a Design Pattern Really Is - and What It Is Not
The Three GoF Families
The Complete Catalog of All 23 GoF Patterns
The Patterns You Will Meet Most Often in Java
Deep Dives with Java Examples
How to Recognize Patterns in Existing Code
GoF Patterns and SOLID Principles
Where the Patterns Appear in the Java Ecosystem
Pattern Comparisons That Prevent Common Mistakes
Practical Application: Refactoring an E-Commerce Checkout
Overengineering: When Not to Use a Pattern
Decision Guide and Pattern Selection Matrix
Conclusion
1. Introduction: Why Design Matters More Than Working Code
Imagine two systems that both pass their tests and both satisfy today's business requirements. In the first, every new payment method forces developers to modify a long conditional, touch several controllers, update tests in unrelated modules, and hope that an old integration does not break. In the second, a new payment method is introduced by adding one implementation behind a stable interface. The business result may be identical today. The difference becomes visible tomorrow.
That difference is design. Good design is not about making code look sophisticated. It is about arranging responsibilities and dependencies so that inevitable change has a controlled cost. The GoF design patterns became important because they gave software developers a shared vocabulary for structures that repeatedly solve this kind of problem. A developer can say “Strategy,” “Adapter,” or “Facade,” and communicate a design intention that would otherwise require paragraphs of explanation.
A design pattern is therefore not a library, a framework, or a code snippet to copy. It is a reusable way of thinking about a recurring design problem. The same pattern can look different in Java, C#, Python, or TypeScript because the pattern lives at the level of responsibilities and collaboration, not at the level of syntax.
A useful question to carry through this article
If your system receives five new variations of the same business rule next month, which classes will have to change? The answer often reveals whether a pattern could help.
For an individual developer, learning patterns improves code reading, refactoring, technical communication, interviews, architectural discussions, and the ability to understand frameworks that otherwise seem “magical.” For teams and organizations, the benefit is larger: a shared design vocabulary reduces ambiguity, improves maintainability, helps isolate external dependencies, and makes long-lived systems easier to evolve.
There is also a broader social dimension. Modern society depends on software in banking, healthcare, transportation, communication, government, education, and infrastructure. Maintainable software is easier to test, safer to change, and less expensive to keep alive. Design patterns do not guarantee good software, but the disciplined thinking behind them can contribute to more reliable digital systems and more sustainable engineering practices.
2. From Architecture to Software: The History Behind Design Patterns
The idea of a “pattern” did not begin in software. Architect Christopher Alexander and his collaborators described recurring solutions to recurring design problems in the built environment. The key idea was not to prescribe one rigid blueprint, but to capture a relationship among context, problem, forces, and solution so that practitioners could reuse accumulated experience without copying a building literally.
Software engineers recognized a powerful analogy. Object-oriented systems also faced recurring forces: object creation should be flexible, algorithms should be replaceable, incompatible interfaces should cooperate, complex subsystems should be simplified, and objects should communicate without becoming entangled in a web of dependencies.
In 1994, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides published Design Patterns: Elements of Reusable Object-Oriented Software. Because of the four authors, the book became known as the Gang of Four, or GoF, book. Its enduring contribution was not that every idea was invented from nothing. The authors observed recurring structures, named them, documented their intent, described their participants and consequences, and gave developers a vocabulary for discussing object-oriented design.
The book cataloged 23 patterns in three families: creational, structural, and behavioral. Decades later, the details of software development have changed dramatically - cloud computing, microservices, containers, reactive systems, serverless platforms, dependency injection, functional programming, and event streaming are now common. Yet the core questions remain recognizable: How do we create objects? How do we compose behavior? How do we isolate change? How do we coordinate collaboration? That is why GoF patterns are still useful.
Figure 1: patterns travel from architecture to software - what changes is the technology, not the recurring design forces.
3. What a Design Pattern Really Is - and What It Is Not
3.1 A pattern is a model of a solution
A pattern describes an arrangement of responsibilities that has repeatedly proven useful in a certain context. Strategy, for example, says that when multiple algorithms serve the same purpose and must vary independently from the client, you can encapsulate those algorithms behind a common abstraction and make the client depend on that abstraction. It does not require specific class names or a specific number of classes.
3.2 A pattern is not copy-and-paste code
Copying a textbook implementation without understanding the forces behind it is one of the fastest ways to turn patterns into accidental complexity. The implementation should fit the language, framework, scale, testing strategy, and domain. In modern Java, a Strategy may be a class hierarchy, a functional interface implemented with lambdas, a collection of Spring beans, or a map from business keys to functions.
3.3 A pattern has consequences
Every design decision introduces trade-offs. A pattern can reduce coupling while increasing indirection. It can improve extensibility while increasing the number of types. It can clarify responsibilities while making the control flow less obvious to a newcomer. Pattern literacy therefore requires two skills: recognizing when the forces justify the structure, and recognizing when the structure would be heavier than the problem.
3.4 Patterns as a shared language
Perhaps the greatest long-term value of GoF is communication. “Use an Adapter at the payment boundary” says much more than “create another class.” The name suggests intent: translate an external interface into an internal one so the domain does not speak the vendor's language. Shared vocabulary allows architecture discussions to move from syntax to design.
4. The Three GoF Families
The three GoF families, the question each one answers and the patterns it contains.
Family
Core question
Patterns
Creational
How can objects be created without coupling clients to concrete construction details?
The categories are useful as a map, but real designs often combine patterns. An application might use Factory Method to choose a Strategy, wrap the Strategy with a Decorator, expose a simplified operation through a Facade, publish results through Observer-like events, and place an Adapter around an external API. The value is not in stacking patterns; it is in giving each recurring problem an appropriate structure.
Figure 2: the 23 patterns organized by the question each family answers - five creational, seven structural and eleven behavioral.
5. The Complete Catalog of All 23 GoF Patterns
The following catalog is intentionally practical. For each pattern, focus on the design pressure it relieves rather than trying to memorize a UML diagram.
5.1 Abstract Factory (Creational)
Create families of related objects without binding the client to their concrete classes.
Typical applications: A UI toolkit with light and dark component families; a cloud abstraction that creates storage, queues, and database clients for AWS or Azure.
Design signal
Use it when products must vary together as a coherent family. Avoid it when only one product varies or when introducing a new product type would force widespread factory changes.
5.2 Builder (Creational)
Construct a complex object step by step while making optional parameters and construction rules explicit.
Typical applications: HTTP requests, reports, configuration objects, queries, domain aggregates, DTOs with many optional fields.
Design signal
Use it when constructors become unreadable, construction has stages, or validation belongs at build time. Do not use it merely to make every simple object fluent.
5.3 Factory Method (Creational)
Define a creation operation whose concrete product can vary without making the client depend directly on concrete classes.
Use it when creation decisions should be extensible or localized. Distinguish it from a Simple Factory, which is useful but is not one of the formal 23 GoF patterns.
5.4 Prototype (Creational)
Create new objects by copying a configured prototype rather than reconstructing them from scratch.
Useful when configuration is expensive or cloning is semantically natural. Be careful with shallow versus deep copies and shared mutable state.
5.5 Singleton (Creational)
Ensure a class has one controlled instance and provide access to it.
Typical applications: A truly unique process-wide coordinator or resource whose uniqueness is part of the requirement.
Design signal
Use cautiously. Global access can hide dependencies, harm tests, and create global mutable state. Dependency-injection containers often remove the need for manual Singletons.
5.6 Adapter (Structural)
Convert the interface of one component into the interface another component expects.
Use when behaviors must be composed in many combinations and subclassing would explode. Watch the order of decorators because composition order may affect semantics.
5.10 Facade (Structural)
Provide a simpler, higher-level interface to a complex subsystem.
Powerful for decoupling emitters from independent reactions. In distributed systems, message brokers implement related publish/subscribe ideas but add delivery and consistency concerns.
5.20 State (Behavioral)
Let an object change its behavior when its internal state changes, usually by delegating state-specific behavior to state objects.
Typical applications: Order lifecycle, document workflow, connection state, approval processes.
Design signal
Useful when state-driven conditionals dominate a class and each state has distinct transitions and behavior.
5.21 Strategy (Behavioral)
Encapsulate interchangeable algorithms behind a common abstraction.
Powerful when element types are stable and operations change frequently; awkward when new element types are added often.
6. The Patterns You Will Meet Most Often in Java
There is no universal ranking of pattern frequency. Usage depends on the domain, architecture, framework, and team style. In enterprise Java and backend systems, however, a recurring group appears explicitly or is embodied by frameworks: Strategy, Factory Method, Builder, Adapter, Observer, Decorator, Facade, Singleton, Template Method, and Command.
The ten patterns most often met in enterprise Java, the signal that reveals them and the gain they bring.
Pattern
Typical signal in code
Why it helps
Strategy
Several algorithms serve the same purpose and a switch/if chain keeps growing.
Moves variation behind a stable abstraction and improves independent testing.
Factory Method
Business code directly chooses among many concrete constructors.
Separates creation decisions from use.
Builder
Long constructors contain many nulls, booleans, or optional arguments.
Makes construction readable and centralizes validation.
Adapter
Domain code imports vendor-specific types and conversion rules.
Creates an anti-corruption boundary around external dependencies.
Observer
One event causes several independent reactions.
Allows new listeners without changing the emitter.
Decorator
Many combinations of optional behavior create subclass explosion.
Composes behavior dynamically.
Facade
A controller or client must coordinate too many subsystem services.
Creates a simple entry point for a use case.
Singleton
Exactly one instance is a real domain/runtime requirement.
Controls uniqueness - but should not be used as a global-access convenience.
Template Method
Several workflows share the same sequence but differ in a few steps.
Centralizes the invariant algorithm while exposing extension points.
Command
Actions must be queued, logged, scheduled, retried, or undone.
Turns operations into first-class objects.
7. Deep Dives with Java Examples
The next sections follow a repeatable learning model: first see the design problem, then the pattern, then the code-level gain. This prevents pattern names from becoming detached from the pressures that make them valuable.
7.1 Strategy Pattern in Java - Replace Conditional Algorithms with Interchangeable Behavior
Suppose an e-commerce system calculates shipping. The first version supports SEDEX, standard delivery, and store pickup. A single method with if/else branches is understandable. But then new carriers, express delivery, same-day delivery, international shipping, and promotional shipping arrive. The calculation class becomes the place where every shipping rule collides.
The calculator now owns the workflow “calculate shipping,” while each Strategy owns one algorithm. A new shipping policy can be added without editing the calculator. Each policy can be unit-tested in isolation, and runtime selection becomes straightforward.
Figure 3: Strategy moves the variation out of the context - the switch stops growing and each algorithm becomes independently testable.
In Java 8+, Strategy can be lightweight when the abstraction is a functional interface: ShippingStrategy free = weight -> 0.0;. The point is not the number of classes; the point is to move algorithmic variation out of the context.
Code gain
Fewer conditionals, lower coupling, isolated algorithms, simpler tests, and stronger alignment with the Open/Closed Principle.
7.2 Factory Method in Java - Separate Creation from Use
Creation logic becomes a design problem when clients repeatedly decide which concrete implementation to instantiate. If notification code is scattered with new EmailNotifier(), new SmsNotifier(), and new PushNotifier(), changing construction rules requires touching business code that should care only about sending a notification.
The classic Factory Method places the creation operation in a creator abstraction and lets concrete creators determine the product. A centralized static method with a switch is commonly called a Simple Factory. It can be perfectly useful, but it is not one of the formal 23 GoF patterns.
Code gain
Business logic depends on product abstractions, creation becomes localized or extensible, concrete constructors stop spreading through the codebase, and tests can replace creation paths more easily.
7.3 Builder Pattern in Java - Make Complex Construction Read Like a Story
Long constructors are dangerous not because constructors are inherently bad, but because positional arguments stop communicating meaning. Consider new User("Ana", "ana@example.com", null, "London", true, false, null). A reader must inspect the constructor signature to know what true and false mean, which null is a phone number, and which fields are optional.
User user =User.builder().name("Ana").email("ana@example.com").city("London").active(true).build();
A Builder can also enforce invariants at build time. The build() method can reject a missing required field, normalize data, or choose defaults. This makes construction a controlled boundary rather than a passive assignment sequence.
Code gain
Readable construction, fewer parameter-order errors, natural support for optional values, centralized validation, and less pressure to create multiple telescoping constructors.
7.4 Adapter Pattern in Java - Protect the Domain from External APIs
An external payment library may accept amounts in cents, return string status codes, use vendor-specific exceptions, and expose terminology that does not belong in your domain. If those details spread through services and controllers, the vendor has effectively rewritten your application's language.
The rest of the application speaks PaymentGateway, not LegacyPaymentClient. If the provider is replaced, the conversion logic changes at the boundary instead of everywhere. This is more than interface compatibility: it is architectural insulation.
Figure 4: the Adapter is an anti-corruption boundary - vendor formats stop at the edge and the domain keeps its own language.
Code gain
External formats and types stay at the edge, the domain depends on its own abstraction, vendor replacement is cheaper, and tests can use a fake PaymentGateway.
7.5 Observer Pattern in Java - Decouple an Event from Its Reactions
When an order is paid, the system may send an email, update inventory, issue an invoice, refresh analytics, and notify partners. If the payment service calls every reaction directly, it knows all consumers and becomes a coordination hub that must change each time a new reaction is added.
The in-memory GoF pattern and distributed publish/subscribe are not identical: brokers introduce durability, delivery semantics, ordering, retries, and consistency. Yet the design intuition is related - the producer should not have to know every independent reaction.
Code gain
New listeners can be introduced without modifying the emitter, responsibilities are separated, and the design becomes a natural stepping stone toward event-driven thinking.
7.6 Decorator Pattern in Java - Compose Optional Behavior without Subclass Explosion
Suppose a notification can be logged, encrypted, measured, retried, and audited. Subclassing every combination quickly produces EmailWithLog, EmailWithLogAndEncryption, SmsWithAuditAndMetrics, and so on. Decorator uses composition instead: each wrapper implements the same abstraction and delegates to another instance.
Java I/O is the classic familiar example: a BufferedInputStream wraps another InputStream and preserves the same broad abstraction. The same idea appears in middleware, security filters, HTTP clients, observability, and cross-cutting concerns.
Code gain
Behavior becomes independently composable, subclass explosion is avoided, and the order of wrappers can be configured at runtime.
7.7 Facade Pattern in Java - Turn a Subsystem into a Clear Use-Case Entry Point
A checkout endpoint should not need to understand inventory validation, shipping calculation, payment capture, invoice creation, persistence, and customer notification. When a controller coordinates all those details, presentation code becomes coupled to the internal topology of the subsystem.
A Facade creates a convenient higher-level interface. It does not mean every internal service must become private or inaccessible. The objective is to give common use cases a stable entry point and prevent callers from depending on unnecessary details.
Code gain
Smaller controllers, centralized orchestration, lower client-to-subsystem coupling, and more freedom to evolve internal components.
7.8 Singleton Pattern in Java - Understand the Requirement Before the Convenience
Singleton is famous because the implementation is easy to recognize. That fame can be misleading. The important question is not “how do I write getInstance()?” but “is uniqueness truly part of the model?” If the real motivation is simply “I want to access this object from everywhere,” the result is usually hidden global coupling.
An enum can provide a robust JVM-level singleton in certain cases, but modern dependency injection often offers a better design. A Spring bean may have singleton scope while still being injected explicitly into consumers. That makes dependencies visible in constructors and easier to replace in tests.
Code gain - only when justified
Controlled uniqueness and consistent lifecycle. Cost when misused: global state, hidden dependencies, test friction, initialization coupling, and concurrency complexity.
7.9 Template Method in Java - Keep the Algorithm Stable, Vary Selected Steps
CSV and JSON importers may share an overall sequence: open, validate, read, transform, save, finalize. If every importer reimplements the full flow, stable parts are duplicated. Template Method places the algorithm skeleton in a base class and delegates selected steps to subclasses.
publicabstractclassImporter{publicfinalvoidimportData(){open();validate();Object data =read();Object transformed =transform(data);save(transformed);finish();}protectedabstractvoidvalidate();protectedabstractObjectread();protectedabstractObjecttransform(Object data);protectedvoidopen(){}protectedvoidsave(Object data){}protectedvoidfinish(){}}
The trade-off is inheritance. If variations become numerous or need to combine independently, composition may be more flexible. Template Method is strongest when the workflow itself is stable and the extension points are intentionally limited.
Code gain
Common order is centralized, duplication falls, and extension points become explicit.
7.10 Command Pattern in Java - Turn Actions into Objects
A button, queue, scheduler, or workflow engine should not need to know the implementation details of every action it can trigger. Command wraps an operation in an object that exposes a common execution contract.
publicinterfaceCommand{voidexecute();}
publicfinalclassSaveDocumentCommandimplementsCommand{privatefinalDocumentFile document;publicSaveDocumentCommand(DocumentFile document){this.document = document;}@Overridepublicvoidexecute(){
document.save();}}Queue<Command> jobs =newArrayDeque<>();
jobs.add(newSaveDocumentCommand(document));
Once the action is an object, it can be stored, logged, scheduled, retried, grouped, or paired with an inverse operation for undo/redo. This is the deeper value of the pattern: it changes an operation from control-flow syntax into manipulable data.
Code gain
The trigger is decoupled from execution, actions can be queued or audited, and workflows gain flexible scheduling and retry behavior.
8. How to Recognize Patterns in Existing Code
Pattern recognition is more useful than pattern memorization. The fastest way to learn is to map code smells or design pressures to candidate patterns, then ask whether the trade-offs are justified.
Questions that reveal a design force, and the pattern usually worth considering.
Question to ask
Candidate pattern
Do I have several algorithms for the same task?
Strategy
Is business code full of concrete new expressions used to select implementations?
Factory Method or another creational pattern
Does this constructor have too many arguments or optional parameters?
Builder
Does the domain speak the vocabulary of a third-party API?
Adapter
Do many components independently react to one event?
Observer
Do I need combinations of behavior without one subclass per combination?
Decorator
Must a caller know many subsystem services to complete one business operation?
Facade
Do if/switch branches mostly depend on current state?
State
Does a request pass through multiple optional handlers?
Chain of Responsibility
Do I need to control access, lifecycle, laziness, or remote invocation behind the same interface?
Proxy
Notice the language of the questions: several algorithms, external vocabulary, many listeners, combinations of behavior, current state. These are the forces. The pattern name is simply a compact label for a proven response to those forces.
9. GoF Patterns and SOLID Principles
SOLID and GoF are related but not equivalent. SOLID provides principles that guide the direction of object-oriented design. GoF provides named structures that can help realize some of those principles in recurring situations. A pattern should not be justified merely by saying “it is SOLID,” and a SOLID design does not require a GoF pattern.
9.1 Strategy and the Open/Closed Principle
A large conditional often means every new algorithm modifies an existing class. With Strategy, a new algorithm may be introduced as a new implementation of an existing abstraction. The context can remain closed to modification while the set of strategies stays open to extension. This is a practical form of the Open/Closed Principle.
9.2 Adapter and Dependency Inversion
When the domain depends on PaymentGateway instead of VendorXClient, high-level policy is protected from a low-level detail. The Adapter translates between them. This is closely aligned with the Dependency Inversion Principle: stable policy depends on an abstraction that belongs to the application, not on the concrete vendor interface.
9.3 Facade and responsibility boundaries
A Facade can give a use case a clear orchestration boundary and keep controllers from coordinating details they should not understand. Yet an enormous Facade that knows every subsystem can become a God Object. Patterns do not remove the need for responsibility discipline.
9.4 Interface Segregation and pattern interfaces
Patterns often introduce interfaces, but “more interfaces” is not automatically better design. The interface should express a meaningful role. A strategy with one focused operation is often natural; an interface that merely mirrors a huge concrete class may provide little decoupling. SOLID helps assess the quality of the abstractions that patterns rely on.
10. Where the Patterns Appear in the Java Ecosystem
10.1 Spring
Spring uses many ideas that are naturally discussed with pattern vocabulary. Dependency injection and the IoC container are related to object creation and factories; application events resemble Observer; AOP commonly relies on proxies; strategy-like interfaces appear throughout the framework; and template classes historically centralize stable workflows while exposing callbacks. Framework internals do not always match textbook GoF structures exactly, but the vocabulary makes the architecture easier to reason about.
10.2 Hibernate and JPA
Lazy-loading entities may be represented through proxies. EntityManagerFactory clearly embodies a creation role. Persistence frameworks also employ enterprise patterns beyond GoF - such as Unit of Work and Identity Map - demonstrating that GoF is a foundation, not the complete universe of software patterns.
10.3 Java I/O
The InputStream hierarchy is a canonical demonstration of Decorator-like composition. A BufferedInputStream wraps another InputStream and adds buffering without changing the client's conceptual contract. Once you recognize this structure, nested stream construction stops looking arbitrary.
10.4 Collections and Iterator
Iterator is built directly into the Java Collections ecosystem. The enhanced for loop hides the mechanics, but the design principle remains: clients traverse elements without depending on the collection's representation.
10.5 Dependency injection and the modern Singleton conversation
Framework-managed singleton scope should not be confused with global static access. A dependency-injection container can manage one instance while preserving explicit dependency relationships. That distinction is central to using lifecycle scopes without reproducing the testing and coupling problems of manual Singleton implementations.
11. Pattern Comparisons That Prevent Common Mistakes
Pairs of patterns that are frequently confused, and the difference that separates them.
Patterns
Key difference
Strategy vs State
Both delegate behavior. Strategy represents a chosen algorithm; State represents behavior that changes as the object moves through a lifecycle, often with state-driven transitions.
Decorator vs Proxy
Both wrap a compatible object. Decorator primarily composes responsibilities; Proxy primarily controls access, lifecycle, laziness, security, or remoting.
Adapter vs Facade
Adapter changes an interface so components can cooperate. Facade simplifies a subsystem by offering a higher-level entry point.
Factory Method vs Abstract Factory
Factory Method focuses on a creation operation that subclasses/creators can vary. Abstract Factory creates coordinated families of related products.
Factory Method vs Simple Factory
A Simple Factory centralizes a construction decision, often with a switch. Useful, but not one of the formal 23 GoF patterns.
Builder vs Factory
A Factory decides what object/implementation to create. A Builder focuses on how a complex object is assembled step by step.
Bridge vs Strategy
Bridge separates two structural dimensions that vary independently. Strategy swaps algorithms serving the same role.
Template Method vs Strategy
Template Method uses inheritance to vary steps inside a fixed algorithm. Strategy uses composition to replace an algorithm or policy.
Observer vs Mediator
Observer broadcasts change to interested listeners. Mediator centralizes collaboration among peers to reduce direct cross-dependencies.
Chain of Responsibility vs Decorator
A chain routes a request through handlers that may continue or stop. A decorator wraps a component to add behavior while preserving its interface.
12. Practical Application: Refactoring an E-Commerce Checkout
A small example can show how patterns materialize together without turning the design into a museum of patterns. Consider a checkout service that currently performs six responsibilities in one method: choose a shipping calculation with a switch, call a vendor payment SDK directly, apply promotional rules with another switch, write audit messages, issue an invoice, and notify several downstream components.
12.1 Step 1 - Identify the change axes
Shipping algorithms will grow independently.
Promotion algorithms change frequently.
The payment provider may be replaced.
Auditing and metrics are cross-cutting and optional.
The checkout endpoint should expose one business operation.
Several independent consumers react after payment.
Each statement describes a force. Only after identifying those forces should patterns enter the conversation.
12.2 Step 2 - Map forces to candidate structures
Each force in the checkout, and the structure that answers it.
Force
Candidate
Interchangeable shipping and promotion algorithms
Strategy
External payment SDK with incompatible vocabulary
Adapter
Optional audit/metrics around a gateway or service
Decorator
One clear checkout entry point
Facade
Independent reactions after successful payment
Observer / domain-event style
Selecting a concrete strategy from configuration
Factory Method or a small factory/registry
Figure 5: each force in the checkout gets its own structure - the flow stays the same, but every axis of change moves to its own boundary.
12.3 Step 3 - Keep the domain-facing contracts small
The contracts express business roles. They do not expose cents, vendor status strings, HTTP response objects, or framework-specific details. That is the architectural payoff of the design: stable concepts sit inside; volatile details stay at the edges.
This is still ordinary code. The patterns have not eliminated conditionals, methods, or data. They have moved variation and integration details to boundaries where those concerns can change independently. That is the practical definition of useful design.
12.5 Step 5 - Test the seams
Because the checkout depends on small abstractions, unit tests can inject a fake ShippingPolicy and a fake PaymentGateway. Integration tests can focus specifically on the Adapter. Listener tests can verify independent reactions. The resulting test architecture mirrors the responsibility architecture.
13. Overengineering: When Not to Use a Pattern
One of the most important lessons in the source manuscript is the warning against “using a pattern for vanity.” A two-line function that doubles a number does not need DoubleStrategy, DoubleFactory, DoubleFacade, DoubleBuilder, and five interfaces. Pattern knowledge should reduce accidental complexity, not generate it.
A pattern is worth considering when it solves a real recurring problem, reduces harmful coupling, creates a valuable extension point, clarifies intent, or protects a stable area from volatile details. It is suspect when it merely increases the number of classes, adds indirection without a change pressure, hides logic that was already obvious, or is introduced solely because the pattern is fashionable.
Practical rule
Prefer the simplest design that remains clear under the changes you can reasonably foresee. Refactor toward a pattern when the pressure becomes visible; do not pre-build every possible abstraction for changes that may never happen.
This is why an if statement can be the right solution today and Strategy the right solution six months later. Context decides. The quality of a design is not measured by the number of named patterns it contains.
14. Decision Guide and Pattern Selection Matrix
All 23 patterns with the family they belong to and the situation that makes each one worth considering.
Pattern
Family
Consider it when...
Abstract Factory
Creational
Families of products must vary together
Builder
Creational
Construction has many parameters or ordered steps
Factory Method
Creational
Concrete creation must be decoupled or extensible
Prototype
Creational
Copying a configured model is better than rebuilding
Singleton
Creational
Uniqueness is genuinely required
Adapter
Structural
External interface does not match the application interface
Bridge
Structural
Two dimensions vary independently
Composite
Structural
Leaf and group objects form a tree
Decorator
Structural
Behaviors must be combined dynamically
Facade
Structural
A subsystem is too complex for callers
Flyweight
Structural
Huge object populations duplicate intrinsic state
Proxy
Structural
Access/lifecycle must be controlled behind the same interface
Chain of Responsibility
Behavioral
A request passes through ordered handlers
Command
Behavioral
An action must become storable/manipulable
Interpreter
Behavioral
A small grammar needs evaluation
Iterator
Behavioral
Traversal should hide collection representation
Mediator
Behavioral
Peers have too many cross-dependencies
Memento
Behavioral
State must be restored later
Observer
Behavioral
Many independent consumers react to an event
State
Behavioral
Behavior depends heavily on lifecycle state
Strategy
Behavioral
An algorithm must be interchangeable
Template Method
Behavioral
A workflow is stable but selected steps vary
Visitor
Behavioral
Operations change more often than element types
14.1 A problem-first mental map
Figure 6: start from the question, not from the catalog - each design pressure points to the structure that relieves it.
Need to swap an algorithm? -> Strategy
Need to control/extend creation? -> Factory Method
Complex object construction? -> Builder
Third-party interface mismatch? -> Adapter
Need optional composable behavior? -> Decorator
Subsystem too complex for its callers? -> Facade
Many listeners react to one event? -> Observer
Action must be queued/stored/retried? -> Command
Behavior changes with lifecycle state? -> State
Request passes through processing stages? -> Chain of Responsibility
Two dimensions vary independently? -> Bridge
Tree of leaves and groups? -> Composite
Need access control/lazy/remoting wrapper? -> Proxy
14.2 Reader self-check
Can you explain why a pattern is not a code recipe?
Can you list the 5 creational, 7 structural, and 11 behavioral patterns?
Can you distinguish Strategy from State?
Can you distinguish Decorator from Proxy?
Can you explain why Simple Factory is not one of the formal 23 GoF patterns?
Can you describe how Adapter protects the domain from vendor APIs?
Can you explain why Singleton deserves caution in dependency-injected applications?
Can you identify when an if/switch is still simpler and better than introducing a pattern?
15. Conclusion
The 23 GoF design patterns are most valuable when they stop feeling like 23 recipes and start feeling like 23 lenses for examining recurring design problems. Creational patterns ask how objects can be created without binding the system unnecessarily to concrete construction details. Structural patterns ask how objects and classes can be combined without making the design rigid. Behavioral patterns ask how algorithms, state, communication, and responsibility should be distributed.
In everyday Java, Strategy, Factory Method, Builder, Adapter, Observer, Decorator, Facade, Singleton, Template Method, and Command are especially useful to recognize. But the point is never “to have more interfaces” or “to use a famous pattern.” Strategy is valuable because algorithms can change without dismantling their context. Adapter is valuable because external dependencies stop dictating the domain's language. Builder is valuable because complex construction becomes readable and safe. Facade is valuable because a complex subsystem can expose a clear business entry point. Command is valuable because actions become objects that can be scheduled, retried, logged, or undone.
The deeper lesson is judgment. A good engineer does not start with “Which pattern can I use?” The engineer starts with “What is changing? What is coupled? What is repeated? What should remain stable? What is the simplest design that can absorb the likely change?” Sometimes the answer is a pattern. Sometimes the answer is a straightforward method and one clear if statement.
My assessment is that GoF remains one of the most useful foundations for software design education precisely because it teaches a transferable vocabulary. Modern frameworks, functional constructs, dependency injection, event systems, and cloud architectures may change the implementation shape, but the fundamental design forces remain. Once you can look at a growing conditional and ask “Strategy or State?”, look at a vendor SDK leaking into the domain and ask “Adapter?”, or look at a controller coordinating eight services and ask “Facade?”, the subject has moved from memorization into software design.
Final thought
The best evidence that you understand design patterns is not that you can name all 23. It is that you can explain why one pattern makes a particular change cheaper - and why, in another context, no pattern at all is the better design.
References and Further Reading
Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
Alexander, Christopher; Ishikawa, Sara; Silverstein, Murray et al. A Pattern Language: Towns, Buildings, Construction. Oxford University Press, 1977.
Fowler, Martin. Refactoring: Improving the Design of Existing Code. Addison-Wesley.
Martin, Robert C. Clean Architecture and writings on SOLID principles.
The supplied GoF/Java manuscript, used as the primary editorial basis for this expanded English article.