The Classic Design Patterns:
Where Are They Now?


Brandon Rhodes
code::dive 2022
Wrocław, Poland

Design Patterns (1994)

Book on Object Oriented programming

by Erich Gamma, Richard Helm,
Ralph Johnson, and John Vlissides
(the ‘Gang of Four’)

It described 23 ‘design patterns’
that had become common solutions for
the problems with Object Oriented languages

The goal for this talk:

To make a brief survey of the 23 patterns

Can we learn anything from the ones that survived?
Can we learn anything from the ones that didn’t?

Limitations of this talk —

But that’s okay!

It’s fine if your list of relevant patterns
is different than mine by the time we finish the talk

The goal is for these old patterns to help
us reflect on how we program today

Happily, the Design Patterns book is online,
so you can review the patterns yourself
later to develop your own opinions
if you don’t like mine

https://www.cs.unc.edu/~stotts/GOF/hires/contfso.htm

Some of my own ideas are also on the web

https://python-patterns.guide/

So here we go

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

First, let’s cross off the patterns that aren’t relevant if your programming language offers first-class functions.

What are first-class functions?
Functions that can be:

• Passed as an argument
• Placed in a data structure

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Factory Method — Subclass Application, override a method

Not only does that sound hopelessly awkward today, but even Design Patterns itself, back in 1994, offered a better approach! Two chapters earlier: the Abstract Factory.

# The Abstract Factory

class MyDocumentFactory(DocumentFactory):
    def build():
        return MyDocument()

f = MyDocumentFactory()
a = Application(f)

Q: Why prefer the Abstract Factory to the Factory Method?

A: Because of a fundamental design principle from the Introduction chapter of the Design Patterns book:

Favor object composition over class inheritance.

# What is ‘composition’? You have ‘composed’
# two objects when you give one of them
# a reference to the other:

class MyDocumentFactory(DocumentFactory):
    def build():
        return MyDocument()

f = MyDocumentFactory()  # object 1
a = Application(f)       # object 2

Favor object composition over class inheritance.

Why?

Because putting objects together dynamically
at runtime is more flexible than writing
extra classes at compile time

Factory Method? Class inheritance.
Abstract Factory? Object composition.

Abstract Factory > Factory Method

So, why did Design Patterns include
the Factory Method if a better
alternative already existed?

Because they wanted the book to be a complete catalog
of all common Object Oriented patterns—

they did not limit themselves to best practices.

And the Factory Method,
despite its awkwardness,
was in widespread use.

‘Factory methods pervade toolkits and frameworks.
MacApp … ET++ … Unidraw … Smalltalk-80 … Orbix ORB’

Abstract Factory
Factory Method

But neither one is a pattern we use in modern languages

# First-class functions? Pass a function.

def build_document():
    return MyDocument()

a = Application(build_document)

# First-class types? Just pass the class!

a = Application(MyDocument)
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Template Method — if Application needs you to give it three procedures, it should force you to write a subclass

Favor object composition over class inheritance.
These three methods should live on a separate class.

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

The Prototype Pattern

Example: Imagine we are writing a music composition
app that wants to let users create four kinds of object.

# The objects are of different classes.
# Some of their constructors need arguments.

Rest()        # quarter rest
Note(1)       # whole note
Note(2)       # half note
Note(4)       # quarter note

How could you explain to, say, a menu widget
how to create these objects when menu items are selected?

# In a modern language, you would
# just pass a data structure

menu.add_actions([
    ('Rest', Rest),
    ('Whole note', Note, 1),
    ('Half note', Note, 2),
    ('Quarter note', Note, 4),
])

Q: But what if your language isn’t that powerful?
Are you going to need as many factories
as kinds of object you want to create?

build_rest()
build_whole_note()
build_half_note()
build_quarter_note()

A: The Prototype Pattern says, No!
Simply create examples of the four objects,
and give them each a clone() method

# The Prototype Pattern

r = Rest()
r.clone() → another Rest

n = Note(1)
n.clone() → another Note of length 1

n = Note(2)
n.clone() → another Note of length 1/2

n = Note(4)
n.clone() → another Note of length 1/4
# Thanks to the Prototype Pattern’s `.clone()`
# method, we can give plain object instances
# to the menu instead of factories.

menu.add_action('Rest', Rest())
menu.add_action('Whole note', Note(1))
menu.add_action('Half note', Note(2))
menu.add_action('Quarter note', Note(4))
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Singletons: those annoying global objects that code needs access to but you don’t want to pass everywhere

Do we need Singletons? Sometimes. Maybe.
That’s a big topic of its own.

But, do we need the Singleton Pattern, where the class itself
is contorted to force its use as a Singleton?

No!

# Just write the class normally,
# build a single instance, and provide
# a global name or a global function
# that returns that instance.

import logging

root = logging.getLogger()

Why avoid the Singleton Pattern?

One reason: testing becomes hard and tests
of the singleton become coupled if you really
truly can’t create a fresh instance,
so the tests are having to share.

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

The Strategy Pattern

Example: What if an object needs to be given
a specific paragraph-breaking routine at
runtime, out of 3 that are available?

Strategy Pattern

Put the 3 versions of the routine
in 3 single-method classes, build an
instance of each, and pass them as
constructor arguments at runtime

Strategy Pattern

We can solve this more simply

In any modern language, you solve this problem by just writing 3 functions, and passing one in as an argument.

Done

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

In powerful modern languages, those six patterns disappear because they aren’t needed.

 23
- 6
___
 17
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

There are two patterns
that are also disappearing
from our code, but it’s because
they are so useful—they are now
getting built into our languages.

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Both of them involve a producer object
which is going to build or retrieve items and
a consumer object that wants to iterate over them

In legacy languages,
you have to choose one approach or the other.

Either way, someone’s class has to
suffer callback-style programming.

Solution: generators

(CLU 1975, Icon 1977, Python 2001, then C#, JS, Ruby)

class Ten:             # Before generators,
    n = 0              # you had to write up the
                       # Iterator Pattern yourself
    def next(self):
        self.n += 1
        if self.n > 10:
            raise StopIteration
        return self.n
def ten():             # After generators
    n = 0
    while n < 10:
        n += 1
        yield n

Each time the consumer asks for an item,
the producer runs enough code to reach its
next yield and deliver the item.

So the consumer’s requests for more
items gradually cause the producer
to run all the way to completion.

# So *both* the producer and consumer
# can be written with normal control flow
# statements like `if` `for` and `while`;
# neither is forced to endure callbacks.

def producer(...):
    for item in ...:
        yield item

def consumer(...):
    for item in producer(...):
        ... # operate on `item`
# Passing a producer to a consumer works
# exactly as though you had implemented
# the Iterator Pattern by hand.

p = producer()
result = consumer(p)

except—

Don’t actually let a consumer talk to a producer

# If you have the consumer drive the
# producer directly, your main thread
# loses control.

p = producer()
result = consumer(p)
p = producer()
result = consumer(p)
# Instead, run the producer to completion
# and save the output in a plain flat data
# structure: a list.

plain_list = []
for item in producer():
    plain_list.append(item)

# Then, pass the list to the consumer.

result = consumer(plain_list)

This idea may sound silly when we’re only
talking about one producer and one consumer, but as
an application grows, you should always prefer
shallow call stacks to deep ones

It’s a good day when you can draw a
bright line across your code and say,

‘everything above this line has finished
running and the only thing crossing this
line is a simple inert data structure’

plain_list = []
for item in producer():
    plain_list.append(item)

# -------- bright line --------

result = consumer(plain_list)

Why?

Because data is easier to
reason about than control flow

The Mythical Man-Month by Fred Brooks (1975):

“Show me your flowchart
and conceal your tables,
and I shall continue to be mystified.

Show me your tables,
and I won’t usually need your flowchart;
it’ll be obvious.”

♥ Your Data

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
 23
- 8
___
 15
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Proxy — sends method calls across the network
Memento — saves object state as a binary string
Observer — object offer subscriptions to its changes

So what are their sins?

Proxy Pattern — hides how data is passed across the network
Memento Pattern — hides how data is persisted to disk
Observer Pattern — scatters data rather than unifying it

♥ Your Data

Proxy Pattern

Network APIs between your services should use
a data format you understand (JSON, Protobuf, CSV)
and be hand-crafted to make sense over
a high-latency channel

Good object methods: small, orthogonal, numerous
Good remote procedures: comprehensive, aggregate, few


Example: a class might offer getters and setters
for all of its 11 instance variables; but a network
call would probably return all 11 at once,
and set as many as you want to provide

Memento Pattern

Data should be persisted in a format that’s
independent of your programming language and
that you can access directly using other tools

(JSON, Protobuf, CSV, MySQL, Postgres)

Observer Pattern

Should we really be building forests of objects
that are subscribed to each other’s state changes?

No!

Look on YouTube for Facebook talks about React.

React is the most popular JS library today

jQuery
React

What is the secret to React’s success?

          Clicks, Keystrokes, Server events
       ↙
State
(data) ↘
          View

So, React makes a data structure
the foundation of your application

Proxy Pattern
Memento Pattern
Observer Pattern

All three of these patterns
fail to put data first

♥ Your Data

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
 23
-11
___
 12
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Bridge — one real-world object, but two classes

Decorator — a wrapper with the same interface as the object

Decorator — a wrapper with the same interface as the object

Mediator — extra class to take action when widgets update

State — replace if-else stacks with separate objects

Bridge Pattern
Decorator Pattern
Mediator Pattern
State Pattern

Q: What do these have in common?

Bridge Pattern
Decorator Pattern
Mediator Pattern
State Pattern

A: They all break the Object Oriented assumption that one real-world object should become one object in your code.

                Bridge ← class
 Decorator ← Decorator ← class
              Mediator ← class
                         class → State

Bridge Pattern
Decorator Pattern
Mediator Pattern
State Pattern

These four patterns all begin a disassembly of Object Orientation, and start using classes to organize procedures into layers rather than to represent data

data ← objects → actions

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
                Bridge ← class
 Decorator ← Decorator ← class
              Mediator ← class
                         class → State
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Builder Pattern — provides a single object whose API builds and returns an object hierarchy for us

# Example: the matplotlib ‘Axis’ object
# provides methods for building Plot and
# Annotation objects.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot(x, y, c='red')
ax.annotate(x[0], y[0], 'start')
ax.annotate(x[1], y[1], 'end')

Adapter Pattern —
when a class doesn’t offer the interface you need,
write a wrapper class that does, and have its
methods call the underlying object

Example: Python’s socket.makefile() method returns an Adapter that looks like a file object but whose read() and write() methods really call the socket’s recv() and send() methods.

adapter.read() -> socket.recv()
adapter.write() -> socket.send()

Flyweight Pattern — small read-only data can
be shared between object instances.

 Glyph        Glyph

 x      108   x      124
 y       10   y       10
 height  12   height  12
 width    8   width    8
 descent  2   descent  2
 Glyph        Glyph

 x 108        x 124
 y 10         y 10

   ↘          ↙

     CharData

     height  12
     width    8
     descent  2

Example: the integers −5 through 256
in Python are flyweight objects.

Each time a calculation returns an integer in that range,
it’s the same object that gets returned each time.

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Composite Pattern
Chain of Responsibility Pattern
Command Pattern
Interpreter Pattern

These are what I call the ‘Framework Patterns’

Composite — give all nodes in a hierarchy the same interface

DOM = Document Object Model

HTML:

<div>
 <img src="example.jpg">
</div>

JS:

div.tagName  → 'DIV'
img.tagName  → 'IMG'

div.children.length  → 1
img.children.length  → 0

Chain of Responsibility — in a Composite UI hierarchy,
pass unprocessed clicks and keystrokes up to your parent

This is universal and called ‘bubbling’ in the browser

Command — represent user actions as a list of
objects that have both do() and undo() methods

This is universal in browsers and GUI frameworks

It turns out the Command Pattern
is useful in other contexts!

Example: the Django web framework’s
database migrations system

# Django lets you define what your database
# tables should look like using a declarative
# syntax.

from django.db import models

class Question(models.Model):
    text = models.CharField(max_length=200)
    pub = models.DateTimeField('date published')

As your models change, Django saves those database migrations using the Command Pattern, so you can both apply each migration to move forward but also ‘undo’ a migration to recover and move back.

v1 CreateModel
   CreateModel
   AddIndex

v2 AlterModelTable
   AddField

v3 RenameField

Interpreter — uses the Composite Pattern to
represent an Abstract Syntax Tree, and make it
executable by giving each node a method.

      "n + 1"

       ┌───┐
       │add│
       └───┘
       ↗   ↖
┌───────┐ ┌───────┐
│get `n`│ │value 1│
└───────┘ └───────┘
      "n + 1"

      ┌──────┐
      │ add  │
      │eval()│
      └──────┘
       ↗   ↖
┌───────┐ ┌───────┐
│get `n`│ │value 1│
│eval() │ │eval() │
└───────┘ └───────┘
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

Facade—hide a complex system behind a single API class

Problem: to qualify as a Facade Pattern,
an API must live on only one object, but
real-world APIs almost always use several

Examples: two real-world APIs, jQuery and Pandas.

# jQuery: is it a Facade? No, because each
# query returns a separate object.

var paragraphs = $('p'); // jQuery object
paragraphs.find('b')     // another object
# Pandas: is its DataFrame a Facade?

filename = 'transactions.csv'
df = pd.read_csv(filename)    # DataFrame
print(df.size)
print(df.columns)

# No, because interacting with a
# specific column returns a `Series`.

name = df['Name']             # Series
amount = df['Amount']         # Series

Facade: 1 object
Real APIs: n objects

Also, the Design Patterns example of a Facade
provides no access to the underlying subsystem.

But real-world APIs are usually happy
to let you access the lower level directly.

# jQuery:

paragraphs[0]  # raw browser `Element`!

# Pandas:

amount.values()  # raw NumPy array!
CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor

So we’ve made it—we’ve completed our survey.

CreationalStructuralBehavioral
Abstract FactoryAdapterChain of Responsibility
BuilderBridgeCommand
Factory MethodCompositeInterpreter
PrototypeDecoratorIterator
SingletonFacadeMediator
FlyweightMemento
ProxyObserver
State
Strategy
Template Method
Visitor