Pattern Foundations

Learning Objectives

Explain what a design pattern is — and is not.

Read a pattern description: intent, problem, forces, participants, consequences.

Classify patterns as creational, structural or behavioural.

Recognise when a pattern adds ceremony without solving a problem.

Prerequisites: Stage 2.

Intuition and Definition

A design pattern is a named, reusable solution to a recurring design problem in a context, together with its trade-offs. The 23 "Gang of Four" (GoF) patterns from *Design Patterns* (Gamma, Helm, Johnson, Vlissides, 1994) are grouped as:

how objects are created Singleton, Factory Method, Abstract Factory, Builder, Prototype (+ Object Pool, not GoF) how objects are composed

Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy how objects interact and divide responsibility

Strategy, Observer, Command, State, Template Method, Chain of Responsibility, Iterator, Mediator, Memento, Visitor, Interpreter

Every pattern lesson in this stage follows the same anatomy:

What does it do, in one sentence?

Problem & forces What pressures make the naive solution bad?

Which roles (classes/interfaces) take part?

Implementation & client Complete code and how callers use it Benefits, costs, testing impact When it makes things worse Alternatives and combinations

Why It Matters

Patterns give a team a shared vocabulary — "wrap it in a decorator", "this should be a strategy" — that compresses a design discussion into a sentence. They also capture hard-won trade-offs.

Syntax and Language Notes

Many patterns shrink to a few lines in languages with first-class functions: Strategy becomes a lambda, Command a closure, Iterator a generator, Observer an event. Implement the *intent*, not necessarily the class diagram.

# Strategy in Python: often just a function argument
def checkout(cart, pricing=lambda total: total):
    return pricing(sum(cart))
print(checkout([10, 20], pricing=lambda t: t * 0.9))

Complete Example

The same requirement — "the cart total depends on an interchangeable discount rule" — with a hard-coded conditional and with a pattern, to see what the pattern buys.

#include <functional>
#include <iostream>
#include <string>

// without a pattern: every new rule edits this function
double totalWithSwitch(double subtotal, const std::string& rule) {
    if (rule == "none") return subtotal;
    if (rule == "ten-percent") return subtotal * 0.9;
    if (rule == "five-off") return subtotal > 5 ? subtotal - 5 : 0;
    return subtotal;
}

// with Strategy: the rule is a value passed in
using DiscountRule = std::function<double(double)>;
double totalWithStrategy(double subtotal, const DiscountRule& rule) { return rule(subtotal); }

int main() {
    std::cout << totalWithSwitch(40, "ten-percent") << " " << totalWithSwitch(40, "five-off") << "\n";
    DiscountRule tenPercent = [](double s) { return s * 0.9; };
    DiscountRule loyalty = [](double s) { return s >= 30 ? s - 7.5 : s; };   // new rule: no edit elsewhere
    std::cout << totalWithStrategy(40, tenPercent) << " " << totalWithStrategy(40, loyalty) << "\n";
}
36 35
36 32.5

Step-by-Step Walkthrough

The switch version is fine while rules are few and stable.

When rules come from marketing every month, each change edits and re-tests the same function — the force that motivates Strategy.

The strategy version accepts any rule; adding loyalty touched nothing else.

Common Mistakes

Pattern-first design ("let's use Abstract Factory") instead of problem-first.

Implementing the diagram literally in languages where a function or generic would do.

Pattern names without the pattern — calling any wrapper a "Proxy" or any class with create a "Factory", which destroys the shared vocabulary.

Trade-offs and Alternatives

Every pattern trades directness for flexibility. Use the "rule of three": introduce a pattern when the third variation appears or when a variation point is a known requirement.

Real-World Use

The standard libraries are full of patterns: iterators (Iterator), std::function (Command/Strategy), streams with buffers (Decorator), java.util.Collections.unmodifiableList (Proxy), GUI event listeners (Observer).

Practice

Q1. (Easy) Which group does Adapter belong to?

Answer: Structural.

Q2. (Medium) What are "forces" in a pattern description?

Answer: The competing pressures in the problem context (flexibility vs. simplicity, performance vs. extensibility) that a pattern balances.

Q3. (Advanced) Give an example where using a pattern would make code worse.

Answer: Wrapping a single, stable payment provider in an Abstract Factory with interfaces for every product "in case we add more" — more types, indirection and test setup for no current benefit (violates YAGNI). A direct class is clearer until a second provider is real.

Interview Question

Q1. "What is a design pattern, and how do you decide to use one?"

Answer: A named solution to a recurring problem in a context, including its consequences. Decide from the problem: identify the force (e.g. "algorithms must be swappable at run time", "object creation must be hidden from clients"), check whether the pattern's consequences fit (extra indirection, more classes, testability gains), consider the simplest language-native alternative (a lambda, a generic), and only then apply it.

Quick Quiz

Q1. Who wrote the 1994 *Design Patterns* book?

Answer: Gamma, Helm, Johnson and Vlissides (the "Gang of Four").

Q2. Name the three GoF groups.

Answer: Creational, structural, behavioural.

Q3. Are patterns mandatory for good OOP?

Answer: No — they are options with costs.

Summary

Patterns are named solutions with trade-offs, grouped into creational, structural and behavioural.

Start from the problem and forces; use the simplest form your language allows.

Related: Modules 27–51, pattern selection guide (closing chapter).