Head First Design Patterns

By Eric Freeman, Elisabeth Robson (2004)

My Notes

Principles

Composition over inheritance, loose coupling…

Open-Closed

Open to extension, closed to modification: create new behaviour without modifying existing code (strategy, observer, decorator, template pattern…).

Yes, but static languages and fear of change vs functional languages, automated testing, pipelines, etc.

Dependency Inversion

“It suggests that our high-level components should not depend on our low-level components; rather, they should both depend on abstractions.”

Yes, but getting the abstractions right…

Hollywood Principle

High-level components can call low-level components, but not the other way around.

Least Knowledge (Law of Demeter)

Objects should have limited knowledge of other objects/classes, should only talk to immediate “friends.”

Single Responsibility

Cohesion is a more general concept than the Single Responsibility Principle, but the two are closely related. Classes that adhere to the principle tend to have high cohesion and are more maintainable than classes that take on multiple responsibilities and have low cohesion.

Patterns

Strategy

Assign behaviour at runtime vs reyling on the inheritance hierarchy.

Mix and match (dynamic) behaviour; when behaviour doesn’t match the inheritance hierarchy; when behaviour is more likely to change.

Voice {
  +speak()
}

SqueekyVoice extends Voice

MaleAvatar {
  +MaleAvatar(: Voice) //assign voice behaviour
  +speak() {
    voice.speak()
  }
}

Observer

Communicate between objects, without the subject knowing anything about the observer(s).

interface Observer {
  +update() //parameters optional, but more coupled
}

interface Subject {
  +registerObserver(: Observer)
  +unregisterObserver(: Observer)
  -updateAllObservers()
}

Pub/sub is less coupled yet: each only know about the message broker, not each other.

Decorator

Modify the behaviour of an object at runtime, with an arbitrary number of decorators.

Wrap an object in another with the same interface; add funcionality before or after delegating to the decorated object. Internally the decorator classes reference and make use of the wrapped instances.

interface CoffeeDecorator extends Coffee

Mocha extends CoffeeDecorator
SoyMilk extends CoffeeDecorator

Drink drink = new SoyMilk(new Mocha(new Coffee()))
drink.cost() //SoyMilk.cost() + Mocha.cost() + Coffee.cost()

Factory Method

Separate object creation from object use (instantiate objects elsewhere in the code).

Abstract Factory

Helps create families of objects.

Singleton

A single object instance and central access to it.

Singleton {
  -instance
  +getInstance() //static; create instance if not exists
}

Command

Contains the actions (methods/functions) and the reciever (object/data), or…

With lambda expressions, instead of instantiating the concrete command objects, you can use function objects in their place. In other words, we can use a function object as a command. And, while we’re at it, we can delete all those concrete Command classes, too.

E.g., UI components.

Adapter

Translate between interfaces. E.g., insert an adapter between a new back-end interface without changing the front-end legacy code.

Decorator adds or modifies functionality; this is a simple translator.

Facade

Simplify/reduce the work done by clients. E.g., a bunch of method calls that are tedious, complex, or error-prone.

Template Method

Adapt part(s) of an algorithm with subclasses.

Reader {
  +open()
  +parse() //default impl
  +process() {
    open()
    parse()
  }
}

JSONReader extends Reader
  +parse() { //override individual steps as needed
    ...
  }
}

Iterator

Typically,

Iterator {
  +next()
  +hasNext()
  +remove()
}

Composite

A common interface for all elements in a tree structure, whether leaf or “composite.”

State

Represent each possible state in a different class (e.g., IgnitionOff, CarInNeutral, CarInDrive). The reference to the current state changes; all states must implement the same interface.

Player {
  -PlayerState
}

interface PlayerState {
  -Player
  +clickPlay()
  +clickPause()
}

Playing extends PlayerState
Paused extends PlayerState

Proxy

Use a stand-in (duplicate the original interface) to control access to the actual object. E.g., to limit access, implement before/after handlers, manage cache…

Chain of Responsibility

Set up multiple handlers for a single request.

interface Handler {
  -next: Handler
  +handle()
}