Table of contents
- Why Code Keeps Repeating Itself
- Patterns Are Not Recipes
- GoF 23 Pattern Classification
- How to Read a Pattern
- When to Use Patterns
- When Not to Use Patterns
- Relationship with SOLID
- Series Structure
- Development Environment
- Key Takeaways
Why Code Keeps Repeating Itself
After working on a few projects, a strange sense of deja vu sets in. “Haven’t I built this exact structure before?” The if-else chain for routing payment methods, the event listener that fires notifications, the factory class that centralizes object creation. The names and domains differ, but the skeletons are remarkably similar.
Design patterns are names given to these recurring structures. In 1994, four authors cataloged 23 commonly occurring structures in software design into a single book. Known as the GoF (Gang of Four) — Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides — their Design Patterns: Elements of Reusable Object-Oriented Software is that book.
This series surveys the GoF 23 patterns from a practical perspective. Rather than focusing on academic classification, we concentrate on what problem each pattern solves, and implement them in Java and Kotlin.
Patterns Are Not Recipes
When first encountering patterns, it is easy to think “so I just use this code as-is.” Yet from the very preface of the GoF book, the authors make this clear: patterns are not code to copy and paste, but descriptions of the core structure of solutions to recurring design problems.
A good analogy is that patterns are closer to cooking techniques than recipes. If you understand the technique of “stir-frying,” you can make stir-fried vegetables or shrimp fried rice. But unlike a recipe that specifies “50g onion, 1 tablespoon soy sauce,” you need to adapt the technique to your situation. Patterns work the same way. Once you understand the structure of the Strategy pattern, you can apply it to payment method routing, sorting algorithm swaps, or discount policy selection.
Failing to grasp this distinction leads to two traps. One is forcing patterns into every situation. The other is giving up, thinking “there’s no pattern that exactly fits my problem.” Both are consequences of treating patterns as recipes.
GoF 23 Pattern Classification
The GoF categorized patterns into three groups by purpose: Creational, Structural, and Behavioral.
| Category | Patterns | Core Question |
|---|---|---|
| Creational | Singleton, Factory Method, Abstract Factory, Builder, Prototype | How should objects be created? |
| Structural | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy | How should objects be composed? |
| Behavioral | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor | How should responsibilities be distributed among objects? |
The core question for each category is as follows:
- Creational patterns: What problems arise from creating objects directly with
new? What improves by encapsulating the creation process? - Structural patterns: How can classes or objects be composed to build larger, more flexible structures?
- Behavioral patterns: How should objects communicate and distribute responsibilities to create a design that is resilient to change?
You do not need to memorize all 23. About half are commonly used in practice; the rest appear only in specialized situations. What matters is understanding the classification system. When you encounter a new design problem, being able to first ask “Is this a creational problem, a structural problem, or a behavioral problem?” narrows down which patterns to explore.
How to Read a Pattern
In the GoF book, each pattern is described in a consistent format. Knowing these four key elements lets you quickly grasp any pattern.
Intent
A one-line summary of the problem the pattern solves. For example, the Strategy pattern’s intent is: “Define a family of algorithms, encapsulate each one, and make them interchangeable.” Reading the intent lets you judge in 30 seconds whether the pattern fits your problem.
Motivation
The intent alone is often not enough to build intuition. The Motivation section uses a concrete scenario to explain: “In this situation, this inconvenience arises, and therefore this structure is needed.” It is the part that justifies the pattern’s existence.
Structure
Class diagrams and sequence diagrams that show the pattern’s skeleton. They visualize who the participants are and how they relate to each other. This helps you form a mental picture before writing code.
Consequences
A summary of what you gain and lose by applying the pattern. Every pattern has trade-offs. The Strategy pattern makes algorithm swapping easy, but the client must understand the differences between strategies, and the number of objects increases. Skipping this section sets you up for the frustration of “I applied a pattern, but it made things more complex.”
The recommended order for studying patterns in practice is: Intent -> Motivation -> Structure -> Consequences, and then look at the code. Starting with code tells you the “what” but makes you miss the “why.”
When to Use Patterns
The situations where patterns shine are clear.
When isolating points of likely change. If you hide parts with a high probability of changing behind an interface, you only need to swap out the implementation later. Patterns like Strategy, Factory Method, and Observer serve this purpose.
When revealing design intent. Writing if (status == PENDING) ... else if (status == APPROVED) ... versus using the State pattern may produce the same behavior, but the latter expresses the design intent — “behavior varies by state” — through structure. When a teammate reads the code and recognizes “ah, it’s the State pattern,” they can quickly grasp the overall flow.
When used as a shared vocabulary for the team. Saying “let’s apply the Strategy pattern to the payment module” allows team members who know the pattern to visualize the structure without further explanation. This is one of the most practical values of patterns — they reduce the bandwidth needed for design discussions.
When Not to Use Patterns
Pattern abuse is as harmful as not knowing patterns at all. Be skeptical about applying a pattern in the following situations.
When the problem is simple. If a two-line if-else suffices, turning it into a Strategy pattern creates at minimum four files: an interface, implementations, and a context class. Code volume grows, and following the flow requires jumping between multiple files. In such cases, simple code is the better design.
As a quick example, consider a situation where there are only two discount rates.
// An if-else is better for this
fun getDiscount(type: String): Double =
if (type == "VIP") 0.2 else 0.1
Solving this with Strategy would look like:
// An example of over-abstraction
interface DiscountStrategy {
fun calculate(): Double
}
class VipDiscount : DiscountStrategy {
override fun calculate() = 0.2
}
class NormalDiscount : DiscountStrategy {
override fun calculate() = 0.1
}
class PriceCalculator(private val strategy: DiscountStrategy) {
fun getDiscount() = strategy.calculate()
}
If there are only two discount types, this structure is overkill. The pattern becomes justified only when the number of discount types could grow to five, ten, or more.
When you are forcing structure just to name a pattern. If you find yourself thinking “I feel like I should apply some pattern to this code,” that is a warning sign. A pattern should emerge as a solution to a problem that comes first. Reversing the order distorts the design.
When the team does not know the pattern. Applying a pattern that only you understand makes it just complex code to everyone else. The communication benefit of the pattern vanishes, leaving only the learning cost.
The practical decision criterion is surprisingly simple. If the code is not hurting right now, do not apply a pattern. When changes become frequent and painful, when conditional branches keep multiplying, when the same structure is being copy-pasted — that is when you reach for a pattern.
Relationship with SOLID
When studying design patterns, the SOLID principles naturally tag along. SOLID is a set of five object-oriented design principles organized by Robert C. Martin.
| Principle | Name | Core Idea |
|---|---|---|
| S | Single Responsibility | A class should have only one reason to change |
| O | Open-Closed | Open for extension, closed for modification |
| L | Liskov Substitution | Subtypes must be substitutable for their base types |
| I | Interface Segregation | Clients should not be forced to depend on methods they do not use |
| D | Dependency Inversion | High-level modules should not depend on low-level modules |
The relationship between patterns and SOLID is this: if SOLID tells you “what good design is,” design patterns are concrete solutions for “how to solve recurring problems while following those principles.”
For example, the Strategy pattern is a quintessential way to realize the OCP (Open-Closed Principle). When adding a new algorithm, you simply create a new strategy class without modifying existing code. The Observer pattern follows the DIP (Dependency Inversion Principle) — the Subject does not know concrete Observers directly but depends on an abstract interface.
Memorizing patterns without understanding SOLID makes it hard to grasp why patterns are shaped the way they are. Conversely, knowing only SOLID without patterns means you understand the principles but take longer to translate them into actual code. They are most effective when learned together.
Series Structure
This series consists of 8 parts, covering the patterns most frequently encountered in practice from among the GoF 23. The parts are organized not by GoF category but by “what problem they solve.”
| Part | Title | Patterns Covered |
|---|---|---|
| 1 | Introduction to Design Patterns | GoF classification, how to read patterns, when to use and not use them |
| 2 | Strategy and State | Two ways to swap behavior |
| 3 | Observer and Command | Patterns for handling events |
| 4 | Template Method and Chain of Responsibility | Patterns for controlling flow |
| 5 | Factory Method, Abstract Factory, Builder | Designing object creation |
| 6 | Decorator and Proxy | Wrapping to add features or control access |
| 7 | Adapter, Facade, Composite | Patterns for organizing structure |
| 8 | Singleton, Iterator, Prototype | Frequently used but often misunderstood patterns |
Starting from Part 2, we begin with behavioral patterns. There is a reason for covering behavioral patterns before creational or structural ones. The first problems you hit in practice are “where should this logic go?” and “how do I organize these conditional branches?” Questions about how to create objects (creational) or how to compose them (structural) come after that.
Each part starts with the intent and motivation of the pattern, shows the structure through class diagrams and code examples, and concludes with trade-offs and practical tips.
Development Environment
The code examples in this series primarily use Kotlin, supplemented with Java code where it helps clarify a pattern’s structure. Kotlin is chosen because its concise syntax makes it easier to focus on the essence of patterns.
Below is a basic structure used throughout the series. The pattern of defining an interface and plugging in implementations recurs in most patterns.
// Core of the pattern: abstract via interfaces and make implementations swappable
interface PaymentMethod {
fun pay(amount: Int)
}
class CreditCard : PaymentMethod {
override fun pay(amount: Int) {
println("Paying $amount via credit card")
}
}
class KakaoPay : PaymentMethod {
override fun pay(amount: Int) {
println("Paying $amount via KakaoPay")
}
}
If this interface-implementation relationship feels comfortable, you should have no trouble following along with the series. If keywords like interface or override feel unfamiliar, it would be better to brush up on the basics of object-oriented programming first.
Key Takeaways
Here is a summary of what this part covered:
- Design patterns are a catalog of named solutions to recurring design problems. They are techniques, not recipes
- The GoF 23 patterns are classified into Creational (5), Structural (7), and Behavioral (11)
- When reading a pattern, approach it in the order: Intent -> Motivation -> Structure -> Consequences
- Patterns are valuable when they isolate points of likely change, reveal design intent, and serve as a shared team vocabulary
- When the problem is simple, when patterns are being forced, or when the team does not know the pattern, it is better not to use one
- SOLID principles are the theoretical foundation of patterns, and learning both together is most effective
In the next part, we cover Strategy and State as the first behavioral patterns. We will see how these two patterns tidy up code when if-else branches keep growing.




Loading comments...