Design Patterns: David Talby
Design Patterns: David Talby
David Talby
This Lecture
More for a Document Editor
Synchronizing Multiple Windows
Observer
Simplifying complex interactions
Mediator
Working on complex structures
Iterator
Visitor
12. Observer
Define a one-to-many dependency
between objects, so that changing
one automatically updates others
For example, a spreadsheet and
several charts of it are open
Changing data in a window should
be immediately reflected in all
The Requirements
Document and Chart classes must
not know each other, for reuse
Easily add new kinds of charts or
other links
A dynamic number of charts
The Solution
Terminology
Subject and Observer
Publisher and Subscriber
Listeners
Subjects attach and detach
listeners, and notify of events
Clients update themselves after
receiving a notification
The Solution II
Here’s an abstract observer:
class Observer {
void update() = 0;
}
Concrete observers such as
class Chart will inherit Observer
The Solution III
Here’s the (concrete!) subject:
class Subject {
void attach(Observer *o)
{ observers.add(o); }
void detach(Observer *o)
{ observers.remove(o); }
void notify() {
for i in observers do
o->update();
}
protected:
List observers;
}
The Solution IV
Both subject and observer will
usually inherit from other classes
as well
If multiple inheritance is not
available, the observer must be a
separate class that has a reference
to the chart object and updates it
Java has a special mechanism –
Inner classes – to make this easier
The UML
The Fine Print
Observing more than one subject
Update must include an extra
argument to tell who is updating
Observing only certain events
Attach must include an extra
argument to tell which events
interest this observer
Observing small changes
Update includes arguments to tell
what changed, for efficiency
The Fine Print II
Who calls Notify?
Greedy – the subjects, on change
Lazy – the observers, on query
Common errors
Forgetting to detach an object when
it is destroyed
Calling Notify in an inconsistent state
Java includes Observer as part of
the standard libraries
In package java.util
Known Uses
All frameworks of all kinds
MFC, COM, Java, EJB, MVC, …
Handle user interface events
Handle asynchronous messages
13. Mediator
Encapsulate a complex interaction
to preserve loose coupling
Prevent many inter-connections
between classes, which means
that changing their behavior
requires subclassing all of them
For example, a dialog box includes
many interactions of its widgets.
How do we reuse the widgets?
The Requirements
A widget is a kind of colleague
Colleague don’t know about the
interactions they participate in
Can be reused for different dialogs
Colleagues don’t know about others
Allow only O(n) connections
Easy to change interactions
The Solution
All colleagues talk with a mediator
The mediator knows all colleagues
Whenever a colleague changes, it
notifies its mediator
The mediator codes the interaction
logic, and calls operations on other
colleagues
The Solution II
An example interaction:
The Solution III
Only O(n) connections:
The UML
The Fine Print
The interaction logic (mediator) and
colleagues can be reused separately
and subclassed separately
Protocols are simpler since n-to-1
relations replace n-to-m relations
Abstract mediator class is unnecessary
if there’s only one mediator
Observer or mediator?
One-to-many or many-to-many?
Should the logic be centralized?
Known Uses
Widgets in a user interface
Delphi and VB “hide” this pattern
Connectivity constraints in diagrams
14. Iterator
without exposing its representation
An extremely common pattern
For example, a list should support
forward and backward traversals
Certainly not by exposing its
internal data structure
Adding traversal methods to List’s
interface is a bad idea
The Requirements
Traversal operations should be
separate from List<G>’s interface
Allow several ongoing traversals
on the same container
Reuse: it should be possible to
write algorithms such as findItem
that work on any kind of list
The Solution
Define an abstract iterator class:
class Iterator<G> {
void first() = 0;
void next() = 0;
bool isDone() = 0;
G* item() = 0;
}
The Solution II
Each data structure implementation
will also implement an iterator class:
ListIterator<G>
HashTableIterator<G>
FileIterator<G>
StringIterator<G>
Each data structure can offer more
than one iterator:
Forward and backward iterators
Preorder, inorder, postorder
The Solution III
For example:
class BackwardArrayIterator<G>
: public Iterator<G>
{
Array<G> *container;
int pos;
public:
BackwardArrayIterator(Array *a)
{ container = a; first(); }
next()
{ --pos; }
// other methods easy
}
The Solution IV
A data structure’s interface should return
iterators on itself:
class List<G>
{
Iterator<G>* getForwardIterator()
{ return new
ListForwardIterator(this); }
Iterator<G>* getBackwardIterator()
// similarly
}
Now every LinkedList object can have
many active iterators
The Solution V
Writing functions for containers:
void print(Iterator<int>* it)
{
for (it->first();
!it->isOver();
it->next())
cout << it->item();
}
Using them:
print(myList->getBackwardIterator());
print(myTable->getColumnItr(“Age”));
print(myTree->getPostOrderIterator());
The Requirements II
Some iterators are generic:
Traverse every n’th item
Traverse items that pass a filter
Traverse only first n items
Traverse a computed view of items
Such iterators should be coded once
It should be easy to combine such
iterators and add new ones
Their use should be transparent
The Solution
Use the Decorator design pattern
For example, FilteredIterator<G>
receives another iterator and the
filtering function in its constructor
It delegates all calls to its internal
iterator except first() and next():
void next() {
do it->next()
while (!filter(it->item() &&
!it->isOver());
}
The Solution II
It is then easy to combine such
generic iterators
Print square roots of the first 100
positive elements in a list:
print(new LimitedIterator(100,
new ComputedIterator(sqrt,
new FilteredIterator(positive,
list->getForwardIterator()))));
Adding an abstract DecoratorIterator
reduces code size if many exist
The UML
The Fine Print
Everything is a container
Character strings
Files, both text and records
Socket streams over the net
The result of a database query
The bits of an integer
Stream of random or prime numbers
This allows reusing the print, find and
other algorithms for all of these
The Fine Print II
Iterators may have privileged access
They can encapsulate security rights
Kinds of abstract iterators
Direct access iterators
Access the previous item
Robustness issues
Is the iterator valid after insertions or
removals from the container?
Iterators and the Composite pattern
Known Uses
All major standard libraries of
popular programming languages
STL for C++
The new Java containers
New libraries for file, network and
database access in C++ conform
to STL’s iterators as well
15. Visitor
Separate complex algorithms on a
complex data structure from the
structure’s representation
For example, a document is a
composite structure involved in many
complex operations
Spell check, grammar check,
hyphenation, auto-format, …
How do we avoid cluttering Glyph
subclasses with all this code?
The Requirements
Encapsulate complex algorithms
and their data in one place
Outside the data structure
Easily support different behavior
for every kind of Glyph
Easily add new tools
The Solution
Say hello to class Visitor:
class Visitor {
public:
void visitImage(Image *i) { }
void visitRow(Row *r) { }
void visitTable(Table *t) { }
// so on for every Glyph type
}
Every tool is a subclass:
class SpellChecker : public Visitor
The Solution II
Add to Glyph’s interface the ability to
accept visitors:
void accept(Visitor *v) = 0;
Every glyph subclass accepts a visitor
by an appropriate callback:
class Image : public Glyph {
void accept(Visitor *v)
{ v->visitImage(this); }
This way the visitor is activated for the
right kind of glyph, with its data
The Solution III
Initiating a spell check (one option):
Create a SpellChecker object
root->accept(sc);
Graphic non-text glyphs will just ignore
the visit
This is why Visitor includes default
empty method implementations
Composite glyphs also do nothing
They can forward the visit to all their
children. This can be coded once in
CompositeGlyph
The Solution IV
Easy to add operations
Word count on characters
Filters such as sharpen on images
Page layout changes on pages
Works on any glyph
In particular, a dynamic selection as
long as it’s a composite glyph
Adding a tool does not require
recompilation of Glyph hierarchy
The UML
The Fine Print
The big problem: adding new Glyph
subclasses is hard
Requires small addition of Visitor, and
recompilation of all its subclasses
How do we traverse the structure?
Using an iterator
From inside the accept() code
From inside the visitxxx() code
Visitors are really just a workaround
due to the lack of double dispatch
Known Uses
Document Editors
Spell Check, Auto-Format, …
Photo Editors
Filters & Effects
Compilers
Code production, pretty printing,
tests, metrics and optimizations
on the syntax tree
Summary
Pattern of patterns
Encapsulate the varying aspect
Interfaces
Inheritance describes variants
Composition allows a dynamic choice
between variants
Design patterns are old, well known
and thoroughly tested ideas
Over twenty years!