Transcript Document

STAT 598W Lecture 18
Design Patterns
Design Patterns




A "hot topic" in O-O research
Started by the book Design Patterns:
Elements of Reusable Object-Oriented
Software by Gamma, Helm, Johnson and
Vlissides (the Gang of Four).
The idea: some useful patterns of objects and
associations recur in lots of places. So, find
the "best" patterns, and encourage their use.
"Best'' usually means elegant, efficient,
reusable, flexible.
Design Patterns


The idea of formalizing recurring patterns has
origins in architecture; see A Pattern
Language by Alexander, Ishikawa, Silverstein,
Jacobson, Fiksdahl-King and Angel.
“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, in such a way
that you can use this solution a million times
over, without ever doing it the same way
twice.”
Reasons to use



It is much easier to explain the code to someone else
We obtain an immediate toolbox for solving any
problem put in front of us: when confronted with a
programming problem, we can think along the way
“what design pattern is appropriate here?”
Even none of the known patterns are appropriate,
examinations of the problem through the lens of
design patterns will help us have a better
understanding of the problem
Design pattern: common ones
The design patterns can be generally
categorized into three types:
 Creational patterns
 Structural patterns
 Behavioral patterns
Design patterns: Creational



Virtual copy constructor
The use of “clone” in polymorphism
The factory: To allow us to have an object
that spits out objects when we need them
(create objects of the relevant type)
Singleton: One way to implement the factory.
The feature is that there is a single copy of
the object which is accessible from
everywhere, without introducing global
variables and all the difficulties they imply
Design pattern: Structural



Adapter: The adapter is a class that translates an interface into
a form that other classes expect. It is most useful when we wish
to fit code into a structure for which it was not originally
designed, either because we have changed our way of doing
things or because the code originates elsewhere
Bridge: The bridge defines an interface, and acts as an
intermediary between a client class and the classes
implementing the interface
Decorator: The decorator allows to change the behavior of a
class at run-time without changing its interface. We can
decorate as many time as we like because decoration does not
change the interface
Design patterns: Behavioral



Strategy: We defer an important part of algorithm to
an inputted object. This allows us to easily change
how this particular part behaves
Template: Rather than inputting an aspect of the
algorithm, we defer part of the algorithm’s
implementation to an inherited class. The base class
only provides the structure of how the different parts
of the algorithm fits together, it does not specify all
the implementations
Iterator: Iterator is one of the most important
component of the standard library. The algorithms in
STL are performed over iterators.
Elements of a Pattern




Pattern name
Problem
Solution
Consequences
Example Pattern



Name: Adaptor
Intent: Convert the interface of a class into
another interface clients expect. Adaptor
lets classes work together that couldn't
otherwise because of incompatible interfaces.
Motivation: Sometimes a toolkit class that's
designed for reuse isn't reusable only
because its interface doesn't match the
domain-specific interface an application
requires.
Example 1



Suppose we have a linked list class:
Now we need a stack.
We can adapt the linked list class to a stack
by renaming the methods.
Example 1 (cont.)

This is the "class version'' of the
adaptor pattern:
Example 1 (cont.)
It's code looks like:
class linkedStack : private linkedList {
public:
linkedStack() {length = 0;}
void push(char c) {prepend(c); length++;}
char pop() {length--; return del(); }
int size() {return length;}
bool isEmpty() {return (length == 0);}
char top() {return first();}
void reset() {release(); length = 0;}
private:
int length;
};
Example 2





A drawing editor letting users draw and
arrange graphical elements (lines, polygons,
text, etc.) into pictures and diagrams.
There is a Shape class, and many subclasses.
The LineShape and CircleShape and so forth
are easy, but a TextShape is quite difficult.
There is a TextView class in the toolkit, but
its interface is quite different than that of
Shape.
Solution: Define TextShape that adapts the
TextView interface to the Shape interface.
Example 2 (cont.)

Here is the “object version'' of this
pattern, for this example:
Consequences
The object adaptor



Lets a single Adaptor work with many
Adaptees---that is, the Adaptee itself and
all its subclasses (if any).
Makes it harder to override Adaptee
behavior. It will require subclassing
Adaptee and making Adaptor refer to the
subclass rather than the Adaptee itself.
Example 2: Pricing Exotic
Options with MC



Implement the pricing of pathdependent exotic options (by using
Monte-Carlo simulation). Summarize the
statistics obtained from the pricing
procedure
To be specific, price the Asian Options
Use design patterns to solve this
programming problem
Example 2: Identify components
There are four distinct actions we need to
price the path-dependent exotic options:
The generation of the stock price path
The generation of the cash-flows given a stock
price path
The discounting and summing of cash-flows
for a given path
The averaging of the prices over all the paths
a summarization of the statistics

Example 2: Encapsulation





We must encapsulate these actions into different
objects (classes), and this is one of the most
important step in OOP design
For 1, we need to design a “path generator” class
For 2, since it is about the type of the pathdependent option, we should design a
“PathDependent” class
For 1, 2, and 3, they should be all encapsulated in a
class that price the exotic options. Name this class
“ExoticEngine”
For 4, we need to design a “statistics collector” class
to do the average and record the statistics
Example 2: Communications






Path generator gets the times of the spot prices required form the
PathDependent product and then generate a vector of spot values
The ExoticEngine gets information about the possible cash-flow times
from the PahtDependent product, it will use this information to
compute the discounted factors and reserve a vector to store the cashflows
The ExoticEngine gets the vector of spot values from the Path
generator
The Exotic Engine passes the vector of spot values to the
PathDependent product, which passes back the number of cash-flows,
and puts their values into the vector previously reserved
The ExoticEngine discounts the cash-flows and sums them up, it then
passes the total value to the Statistic collector
The Statistics collector returns the final price and the summary of
statistics
Example 2: Design patterns
There are multiple Design Patterns that we can use
The implementation of Statistics collector can be realized by using the
“Strategy Pattern”
The PathDependent class and ExoticEngine class can be implemented by
using the “Template Pattern”, because these two classes are the base
classes which only define the interfaces. The detailed implementation
should be defined later in derived classes
We can use the “Bridge Pattern” to deal with the deep copying actions
We can use the “Adapter Pattern” to generate a random path from some
random number generators
We can use the “Decorator Pattern” to realize some complicated
implementations based on simple ones and maintain the same interfaces
(e.g. Statistical collector)
Example 2: Base classes




“RandomBase” class: the path generator
“PathDependent” class: the path-dependent option
type
“ExoticEngine” class: the exotic option pricing engine
“StatisticsMC” class: the statistics collector class
As can be seen, the base classes are all abstract, they
only define the interfaces we expect the object to
have. The implementations will be defined later in
derived classes. Through this ,we can maximize the
usage of our codes and make things easy to
understand, revise, and maintain
Example 2: Derived class
“AntiThetic” class: the path generator with
variance reduction
 “PathDependentAsian” class: the Asian option
 “ExoticBSEngine” class: the exotic option
pricing engine in the Black-Schole world
 “ConvergenceTable” class: the statistic
collector that can returns the convergence
rate of the pricing procedure
The detailed implementations are defined in
derived classes

A Larger Example:
Designing a Document Editor (Read
by yourself after class)
A WYSIWYG editor called Lexi, similar in
intent to the old MacWrite.
Design problems:





Document structure: what is the internal
representation? This affects everything else!
Formatting: how does Lexi arrange text and
graphics into lines and columns?
Embellishing the user interface: What if we want
to add scroll bars, borders, etc.? These might
change as the interface evolves.
Design Problems (cont.)




Supporting multiple look-and-feel standards: Motif,
Presentation Manager, Mac, Windows… We want
independence.
Supporting multiple window systems: MFC or TCL
or Borland or…
User operations: Buttons, dialogs, pull-down
menus, etc. Provide a uniform mechanism for
accessing this scattered functionality, and undoing
operations.
Spelling checking and hyphenation: How to
support a variety of analytical operations, without
having to make changes in lots of places?
Document Structure



One perspective: a document is just a
collection of basic graphical elements.
User perspective: the physical structure of
lines, columns, figures, tables, etc.
The user wants to manipulate this physical
structure directly, through Lexi's interface,
e.g., it should be possible to move an entire
table, not just the individual lines and
characters.
Internal Representation


Choose an internal representation that
matches the document's physical structure.
It should support



maintaining the physical structure (the
arrangement of lines into columns, etc.)
generating and presenting the document visually
mapping positions on the display to elements in
the internal representation.
Constraints



Text and graphics should be treated
uniformly, so one set of operations works for
both.
The internal representation shouldn't have to
distinguish between a single element and a
group of elements, so arbitrarily complex
structures can be built.
It should be possible to distinguish types of
elements; hyphenating a polygon makes no
sense.
Recursive composition
Text and Graphics
Object Structure
Glyphs

Glyphs have three basic responsibilities.
They know




how to draw themselves,
what space they occupy, and
their children and parent.
This example shows the Composite
pattern.
Glyph Operations
Responsibility
Operations
appearance
virtual void Draw(Window)
virtual void Bounds(Rect)
hit detection
virtual bool Intersects(const Point)
structure
virtual
virtual
virtual
virtual
void Insert(Glyph, int)
void Remove(Glyph)
Glyph Child(int)
Glyph Parent()
Formatting

The Glyph arrangement gives a
representation of the document's physical
structure. But how to determine that
structure?




Determining the structure is Lexi's job; the user
expects line breaking to happen for free.
How will a formatting algorithm be applied to the
data structure?
Formatting algorithms come in many flavors, but
they are mostly all complex. How to keep multiple
algorithms encapsulated, and separate from the
document structure?
Can we change the algorithm at run-time?
Strategy Pattern


A solution is to provide a Compositor
class, having subclasses for the various
formatting algorithms.
Then, a new Glyph subclass called
Composition is added.
Composition/Compositor
Composition/Compositor

A Composition object gets an instance of a
Compositor subclass when it's created, and it
tells the compositor to Compose its glyphs
when something changes (user adds new
glyphs, reformats, etc.).
Responsibility
Operations
what to format
void
SetComposition(Composition)
when to format
virtual void Compose()
Unformatted Composition


An unformatted Composition contains
only the visible glyphs (the document's
basic content, no line breaks).
This probably should never be visible!
Composition/Compositor
Embellishing the User
Interface


Suppose we want to be able to add a border,
and scroll bars (and maybe more).
One way: create subclasses of the
Composition class. How many might we end
up with?



BorderedComposition,
ScrollableComposition,
BorderedScrollableComposition, etc.
Embellishments (cont.)



Maybe object composition is a better
way. Suppose we make another class
called Border.
But does Glyph contain Border, or does
Border contain Glyph?
The latter requires no change in Glyph.
Embellishments (cont.)




Borders have appearance, so they are a kind
of Glyph.
Also, clients shouldn't care whether glyphs
have borders or not.
When clients tell a plain, unbordered glyph to
draw itself, it should do so without
embellishment.
If that glyph is in a border, the client
shouldn't have to do anything differently.
Embellishments (cont.)



So, make Border a subclass of Glyph.
This is the idea of a transparent enclosure.
This combines single-child composition and
compatible interfaces. Clients can't tell if
they're dealing with the component or its
enclosure.
Declare a subclass of Glyph called
MonoGlyph as an abstract class for
``embellishment glyphs.''
The Decorator Pattern
Transparency
MonoGlyph stores a reference to a component and forwards all requests to it. So by
default MonoGlyph is transparent to its user.
void MonoGlyph::Draw(Window w) {
_component.Draw(w);
}
At least one operation of the subclass extends the forwarded behavior:
void Border::Draw(Window w) {
MonoGlyph::Draw(w);
DrawBorder(w);
}
Multiple Embellishments
To get just a border around a composition, create the composition, then ``wrap it'' in a
Border, and then draw it:
Window w = new Window;
Composition comp = new Composition;
Border bordComp = new Border(comp);
bordComp.Draw(w);
To make a bordered, scrollable composition, just repeat the pattern:
Window w = new Window;
Composition comp = new Composition;
Border bordComp = new Border(comp);
Scroller scrBordComp = new Scroller(bordComp);
scrBordComp.Draw(w);
Supporting Multiple Look-andFeel Standards





Can Lexi be easily retargeted to a different interface
standard? Think Motif, X, Windows, Presentation
Manager, and so on.
Suppose we have GIU libraries for each standard.
Suppose they all support the same “widgets,” that is,
dialogs, buttons, scrollbars and so forth.
We don't want hard-coded instances like
ScrollBar sb = new MotifScrollBar;
We might forget to change one, and end up with a
Motif menu a Mac application!
Try This
scrollBar * sb = new guiFactory->createScrollBar;
where guiFactory is an instance of a
MotifFactory class. CreateScrollBar returns
a new instance of the proper ScrollBar
subclass for the look and feel desired.
Abstract Factory Pattern


For clients, the effect is the same as asking
for a MotifScrollBar, but Motif isn't mentioned
by name.
How to make this work?


We'll use an abstract class GUIFactory, that has
methods like CreateScrollBar and CreateButton.
Subclasses implement these operations to return
glyphs such as MotifScrollBar or PMScrollBar.
Abstract Factory Pattern
Getting That Look

If it's clear at compile time what the
look and feel will be, then write
GUIFactory * guiFactory = new
MotifFactory;
Or Do It At Startup
GUIFactory * guiFactory;
const char styleName =
getenv("LOOK_AND_FEEL");
if (strcmp(styleName,"Motif") == 0) {
guiFactory = new MotifFactory;
}
else if (strcmp(styleName,
"Presentation_Manager") == 0) {
guiFactory = new PMFactory;
}
User Operations


Some of Lexi's functionality is through the
WYSIWYG representation (enter/delete text,
select ranges, etc.).
Other functionality is accessed through
menus, buttons and keyboard accelerators.
Things like






create a new document
open, save, print a document
cutting and pasting
changing fonts and styles
changing formatting (alignment, justification)
quitting
User Operations (cont.)




There may be multiple ways of accessing
these features, for instance from the menu,
or by using an accelerator, or clicking a
toolbar tool.
These operations are implemented in many
different classes (so let's try to reduce
dependencies). We also want undo and redo.
The solution is to encapsulate requests.
A pull-down menu is just another kind of
glyph, but one that does something in
response to a mouse-up event.
The Solution




Make MenuItem a subclass of Glyph, with the ability
to do some work in response to a request from a
client (probably an event dispatcher).
One approach: make a subclass of MenuItem for
each operation. But we don't make a glyph subclass
for every letter, so this seems wrong.
Besides, we don't want to repeat the code in multiple
places (for a menu item, a accelerator, a tool).
So, encapsulate the command, then attach it to the
menu item.
Command Pattern
MenuItem-Command
Relationship
Buttons and other widgets can use commands in the same way.
Undoability




Some commands should be reversible.
The command for changing a font could
store the range of text and the old font.
Some commands should be reversible,
but others not.
So add a Reversible() method to
Command that returns a Boolean.
Undoability

Lexi can keep track of a command history,
perhaps a doubly-linked list, like this:
past commands
present

Each circle is a command object. Then undo
and redo simply march along the command
history.
Spell Check and Hyphenation




Once again several algorithms are available,
with time/space/quality tradeoffs.
There are other operations that we’d like to
apply to the whole document, or to part
(searching, word counting, complexity
analysis, etc.).
But the Glyph class shouldn’t have to change
when a new capability is added.
Two problems: Accessing the information,
and doing the analysis.
Accessing the Information




In accessing information, different
Glyphs may store their children in
different data structures.
Also, we may need to search in reverse
order.
Currently, a Glyph uses an integer to
locate its children.
We can improve this:
Accessing the Information
void First(Traversal kind)
void Next()
bool IsDone()
Glyph GetCurrent()
void Insert(Glyph)

The type Traversal is an enumeration, with
values such as PREORDER and REVERSE.
Drawbacks



This approach isn’t very good, because it’s
difficult to add new traversal strategies.
Putting this code in the Glyph classes would
force us to constantly rewrite Glyph.
Once again, encapsulate the concept that
varies, this time by introducing iterators.
Iterator Pattern
Iterator
*
First( )
Next( )
IsDone( )
CurrentItem( )
iterators
PreorderIterator
Array Iterator
currentItem
First( )
Next( )
IsDone( )
CurrentItem( )
First( )
Next( )
IsDone( )
CurrentItem( )
root
Gly ph
...( )
CreateIterator( *
Iterators



We can add new types of traversals
without modifying the glyph classes.
Iterators store their own copy of the
state of traversal, we can have multiple
traversals going simultaneously.
We could also parameterize iterators, so
they would apply to other classes as
well.
Traversals vs. Traversal
Actions



Where should the responsibility for actions be?
In the iterator itself? No!
Different analyses require the same type of
iterator. We could put specific functions like
CheckMe(SpellingChecker checker)
in each glyph, but “that would be wrong.”
Better to use the Visitor pattern.
Visitor Pattern
Visitor
VisitCharacter (Character)
VisitRow (Row)
VisitImage (Image)
Hy phenationVisitor
SpellCheckVisitor
Gly ph
... ()
Accept(Visitor)