Skip to content
ioob.dev
Go back

Design Patterns Part 1 — Why Learn Patterns

· 10 min read
Design Pattern Series (1/8)
  1. Design Patterns Part 1 — Why Learn Patterns
  2. Design Patterns Part 2 — Strategy and State
  3. Design Patterns Part 3 — Observer and Command
  4. Design Patterns Part 4 — Template Method and Chain of Responsibility
  5. Design Patterns Part 5 — Factory Method, Abstract Factory, Builder
  6. Design Patterns Part 6 — Decorator and Proxy
  7. Design Patterns Part 7 — Adapter, Facade, Composite: Patterns for Organizing Structure
  8. Design Patterns Part 8 — Singleton, Iterator, Prototype: Frequently Used but Often Misunderstood
Table of contents

Table of contents

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.

CategoryPatternsCore Question
CreationalSingleton, Factory Method, Abstract Factory, Builder, PrototypeHow should objects be created?
StructuralAdapter, Bridge, Composite, Decorator, Facade, Flyweight, ProxyHow should objects be composed?
BehavioralChain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, VisitorHow should responsibilities be distributed among objects?

The core question for each category is as follows:

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.

PrincipleNameCore Idea
SSingle ResponsibilityA class should have only one reason to change
OOpen-ClosedOpen for extension, closed for modification
LLiskov SubstitutionSubtypes must be substitutable for their base types
IInterface SegregationClients should not be forced to depend on methods they do not use
DDependency InversionHigh-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.”

PartTitlePatterns Covered
1Introduction to Design PatternsGoF classification, how to read patterns, when to use and not use them
2Strategy and StateTwo ways to swap behavior
3Observer and CommandPatterns for handling events
4Template Method and Chain of ResponsibilityPatterns for controlling flow
5Factory Method, Abstract Factory, BuilderDesigning object creation
6Decorator and ProxyWrapping to add features or control access
7Adapter, Facade, CompositePatterns for organizing structure
8Singleton, Iterator, PrototypeFrequently 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:


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.

-> Part 2: Strategy and State


Related Posts

Share this post on:

Comments

Loading comments...


Previous Post
OOP Design Principles Part 5 — Composition over Inheritance
Next Post
Design Patterns Part 2 — Strategy and State