<< Home

Chapter 10: Putting Patterns to Work

This chapter gathers the ideas from earlier sections and lets them walk around in a small but complete example. Real systems use patterns in clusters rather than as isolated decorations. Here you will see how patterns cooperate, how refactoring can reveal useful shapes, and how a modest design grows clearer through simple and well-chosen structure.

Small Application Example

This example builds a tiny note service with creation, listing, and formatting. The goal is not to produce a feature rich tool but to demonstrate how classic patterns settle naturally into a small design. The code uses … to mark omitted details. Each piece is short and easily replaced.

Overview of the components

Three elements drive the example. A storage layer provides persistence, a formatter shapes output, and a coordinator handles requests. The boundaries are simple on purpose so the patterns stay clear rather than disappearing inside unnecessary details.

Applying patterns in the example

The FactoryMethod creates storage engines, Strategy selects a formatter, and a light Facade coordinates high level operations. Together they keep responsibilities tidy and each piece stays small enough for quick reading.

class StorageFactory:
  def create(self, kind):
    if kind == "memory":
      return MemoryStorage()
    if kind == "file":
      return FileStorage("notes.db")
    raise ValueError("unknown storage")

class PlainFormatter:
  def format(self, note): return f"* {note}"

class JsonFormatter:
  def format(self, note): …

class NotesApp:  # facade
  def __init__(self, storage, formatter):
    self.storage = storage
    self.formatter = formatter
  def add(self, text): self.storage.save(text)
  def list(self):
    for n in self.storage.load_all():
      yield self.formatter.format(n)

Running the example

The interaction is simple but shows how the outer shell stays clean because inner components carry their share of responsibility. Switching from plain text to JSON formatting takes one line. Swapping storage engines takes another line. The design remains calm even when the supporting classes change shape.

💡 When demonstration examples feel easy to extend, it often means responsibilities were divided in the right places.

Combining Multiple Patterns

Most real solutions mix patterns. A single feature might use Observer for updates, Decorator for optional behavior, and Adapter to unify incompatible interfaces. These combinations follow a simple rule: let each pattern do one job and let their borders touch cleanly.

Patterns that cooperate

Some patterns complement each other particularly well. A Strategy made of small policies works nicely behind a Facade that hides experiment friendly details. A Builder pairs well with Prototype when new configurations must be spun up quickly. These pairings often appear in frameworks because they promote steady growth rather than tangled expansion.

An example combination

This snippet shows a simple exporter that decorates a writer with compression, then uses a strategy to choose output format. The structure lets new formats or decorators join without disturbing the core exporter.

class GzipWriter:
  def __init__(self, inner): self.inner = inner
  def write(self, data): self.inner.write(compress(data))

class CsvStrategy:
  def encode(self, rows): …

class JsonStrategy:
  def encode(self, rows): …

class Exporter:
  def __init__(self, writer, strategy):
    self.writer = writer
    self.strategy = strategy
  def export(self, rows):
    encoded = self.strategy.encode(rows)
    self.writer.write(encoded)

Avoiding pattern overload

Combining patterns works best when each pattern has a clear job. Overuse leads to extra layers where none are needed. A clean test suite often reveals this because excessive scaffolding makes tests slow to write, slow to read, and slow to trust.

⚠️ When patterns grow faster than features, the design becomes ceremony rather than support. Keep the code useful first and shaped second.

Refactoring Toward Patterns

Patterns are easier to apply when they emerge from the code rather than being forced on it. In practice most pattern use begins with a rough but working structure, then small refactorings guide the design toward familiar shapes.

Recognizing opportunities

Repeated conditionals point toward Strategy. Long creation sequences point toward Builder. Ad hoc wrappers point toward Adapter. When code mutters the same idea in different places, that is usually a signal that a known pattern can concentrate the idea into one location.

A small refactor

This example begins with a block of repeated branching. The refactor extracts policy objects that follow a common interface, which turns the branching into a single call. The … marks removed detail.

def price(item, region):
  if region == "EU": return item.base + vat(item)
  if region == "US": return item.base + sales_tax(item)
  if region == "UK": return item.base + uk_tax(item)
  …

# refactor toward strategy

class EuTax: 
  def apply(self, item): …

class UsTax:
  def apply(self, item): …

class UkTax:
  def apply(self, item): …

def price(item, policy): return item.base + policy.apply(item)

Letting patterns settle naturally

The best designs evolve through steady reshaping. Rather than stamping a pattern onto freshly written code, let small refactorings pull the design into place. This keeps the code expressive without feeling overstructured. Patterns should feel like helpful scaffolding, not forced architecture.