Oliver Haase
Design Patterns
Introduction
‘An instrument, a tool, an utensil, whatsoever it be, if it be fit for
the purpose it was made for, it is as it should be though he
perchance that made and fitted it, be out of sight and gone.’ —
Bibliography
‣ Erich Gamma et al. Design Patterns: Elements of Reusable Object-Oriented Software, Addison–Wesley, 1994, ISBN 78-0201633610.
‣ Elisabeth Freeman et al. Head First Design Patterns, O'Reilly, 2004, ISBN 978-0596007126.
‣ Joshua Bloch. Effective Java, Second Edition, Addison Wesley, 2008, ISBN 978-0-321-35668-0.
The bible for design patterns. The authors
are widely known as the Gang of Four (GoF),
their patterns as GoF patterns.
Not that fast, buddy!
Before we start, let’s do some ground work...
Relationships between Classes (and Interfaces)
‣ Specialization and generalization
‣ Association
‣ Aggregation
‣ Composition
‣ Creation
Association
Weakest form of relationship: A is related to B
‣ Undirected Association: no information about navigability
‣ Unidirectional Association: navigation from instances of A to instances of B
‣ Bidirectional Association: navigation both ways
B A
B A
Aggregation
‣ Somewhat stronger than an association
‣ Models part-of relationship, e.g. B is part of A:
‣ Example:
‣ in Java: A has reference to (collection of) B
B A
Student Course
Composition
‣ Stronger than aggregation
‣ Part-of relationship, with ownership, i.e. lifespan of B depends on A
‣ Example:
B A
Room
Building
Don’t you never forget
this again, or...
Design Patterns - Definition
Design Pattern:
“Each [design] pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem" - Christopher Alexander, 1977.
Christopher Alexander:
born 1936 in Vienna, studied mathematics and architecture (Cambridge),
received PhD in architecture (Harvard), professor for architecture (UoC,
Berkeley), known as the founder of design patterns
Design Patterns - Goals
“Any fool can write code that a computer can understand. Good programmers write code that
humans can understand." - Martin Fowler.
‣ fast to develop, clean solution through solution reuse
‣ simplified maintenance through clean structures and déjà vu effects
‣ improved extensibility through anticipatory design
‣ (partly) opportunity for code generation or language support (e.g. singleton pattern)
‣ (partly) opportunity for class libraries (e.g. observer
pattern)
Principles of Reusable Design
According to GoF, there are two fundamental principles of reusable software design (that also underly most if not all patterns):
1 st Principle of Reusable Design:
Program against an interface, not an implementation.
2 nd Principle of Reusable Design:
Prefer composition over inheritance.
Excursion: 1 st Principle in Java
How it’s usually done:
public interface HelloSayer {
String getGreeting(String name);
}
public class NiceHelloSayer implements HelloSayer { @Override public String getGreeting(String name) { return “hello “ + name + “, how are you?”;
} }
Usage:
HelloSayer helloSayer = new NiceHelloSayer();
helloSayer.getGreeting(“John Doe”);
NiceHelloSayer niceHelloSayer = new NiceHelloSayer();
niceHelloSayer.getGreeting(“John Doe”);
...Or:
Excursion: 1 st Principle in Java
Implementation with nested class (J. Bloch, Effective Java):
public final class NiceHelloSayer {
private static final class Implementation implements HelloSayer { @Override public String getGreeting(String name) {
return "hello " + name + ", how are you?";
} }
public static Implementation getInstance() { return new Implementation();
}
private NiceHelloSayer() {
throw new AssertionError();
} }
Excursion: 2 nd Principle
Inheritance breaks encapsulation, leads to strong coupling between super and subclass.
Fragile Base Class Problem: Seemingly correct subclass can
break when superclass is (correctly) changed.
Excursion: 2 nd Principle
Assume, you want to implement an InstrumentedHashSet
that keeps book of the number of attempted add operations:
@ThreadSafe
public class InstrumentedHashSet<E> extends HashSet<E> { private int addCount = 0;
public InstrumentedHashSet() {}
public InstrumentedHashSet(int initCap, float loadFactor) { super(initCap, loadFactor);
}
@Override public boolean add(E e) { addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
Excursion: 2 nd Principle
Problem: HashSet internally uses add to implement addAll
elements added by addAll get counted twice!
Excursion: 2 nd Principle
Second attempt without overriding addAll :
@ThreadSafe
public class InstrumentedHashSet<E> extends HashSet<E> { private int addCount = 0;
public InstrumentedHashSet() {}
public InstrumentedHashSet(int initCap, float loadFactor) { super(initCap, loadFactor);
}
@Override public boolean add(E e) { addCount++;
return super.add(e);
}
public int getAddCount() { return addCount; } }
Excursion: 2 nd Principle
Solution using Composition and Delegation:
@ThreadSafe
public class InstrumentedSet<E> implements Set<E> { private int addCount = 0;
private Set<E> s;
public InstrumentedSet(Set<E> s) { this.s = s; }
public int getAddCount() { return addCount; } @Override public boolean add(E e) {
addCount++;
return s.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) { addCount += c.size();
return s.addAll(c);
}
@Override public void clear() { s.clear(); } // similarly override all other Set methods }
Excursion: 2 nd Principle
‣ agnostic of specific Set implementation, works not only for
HashSet
‣ more boilerplate code (due to lack of language support)
‣ InstrumentedSet independent of changes in the specific
Set implementation.
‣ loose coupling
Solution by Composition
Excursion: 2 nd Principle
Now, from the perspective of extending a threadsafe class while preserving its synchronization strategy.
Example: provide a thread-safe list implementation with additional putIfAbsent method (Goetz et al., Java
Concurrency in Practice)
your check-then-act warning bells should ring!
Excursion: 2 nd Principle
Solution by Inheritance: Extend synchronized Vector class (Java specifies that Vector synchronizes on its intrinsic lock)
@ThreadSafe
public class BetterVector<E> extends Vector<E> { public synchronized boolean putIfAbsent(E x) { boolean absent = !contains(E);
if ( absent ) { add(x);
}
return absent;
} }
Drawback:
Excursion: 2 nd Principle
@ThreadSafe
public class ImprovedList<E> implements List<E> { private final List<E> list;
public ImprovedList(List<E> list) { this.list = list;
}
public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(E);
if ( absent ) { list.add(x);
}
return absent;
}
public synchronized void clear() { list.clear(); } // … similarly delegate all other list methods
}
Solution by Composition
Excursion: 2 nd Principle
‣ agnostic of specific list implementation
‣ extra layer of synchronization, in fact Java monitor pattern
‣ certain performance penalty
‣ more boilerplate code (due to lack of language support)
‣ ImprovedList ’s synchronization policy independent of changes in the specific list implementation.
Solution by Composition
Principles of Reusable Design
Key motivation behind both design principles:
Holy Grail of Reusable Software Design:
Avoid code dependencies to the largest extent possible.
Design Patterns - Categorization
GoF categorize design patterns based on two criteria:
1. Purpose:
• creational: patterns for object creation
• structural: patterns that compose classes and objects
• behavioral: patterns that distribute responsibility for behavior across classes and objects, and make them interact
2. Scope:
• class based: based on relationships between classes, mainly inheritance
• object based: based on relationships between objects
Creational Patterns
“The way to get started is
to quit talking and begin
doing" - Walt Disney.
A Simple, Motivating Example...
Consider an oversimplified document manager that can create, open, and close LaTeX documents:
@ThreadSafe // assuming that LatexDoc is threadsafe public class DocManager {
private final Collection<LatexDoc> docs;
public DocManager() {
docs = new ConcurrentLinkedQueue<LatexDoc>();
}
public void createDoc() {
LatexDoc doc = new LatexDoc();
docs.add(doc);
doc.open();
}
But...
... what about the 1 st principle of reusable software design?
Ok, make LatexDoc an implementation of an interface:
open() close()
Document
open() close()
LatexDoc
Then...
Use Document , rather than LatexDoc , in DocManager :
@ThreadSafe // assuming that LatexDoc is threadsafe public class DocManager {
private final Collection<Document> docs;
public DocManager() {
docs = new ConcurrentLinkedQueue<Document>();
}
public void createDoc() {
Document doc = new LatexDoc();
docs.add(doc);
doc.open();
}
public void openDocs() {