Case Studies Using Patterns
The following slides describ e several case
Design Pattern Case Studies with
studies using C++ and patterns to build
highly extensible software
C++
The examples include
1. Expression trees
Douglas C. Schmidt
{ e.g., Bridge, Factory, Adapter
2. System Sort
http://www.cs.wustl.edu/schmidt/
{ e.g., Facade, Adapter, Iterator, Singleton,
Factory Metho d, Strategy, Bridge, Double-
Checked Lo cking Optimization
Washington University, St. Louis
3. Sort Veri er
{ e.g., Strategy, Factory Metho d, Facade, It-
erator, Singleton
2 1
Case Study 1: Expression Tree
Expression Tree Diagram
Evaluator
BINARY
The following inheritance and dynamic bind-
NODES
example constructs expression trees
ing *
{ Expression trees consist of no des containing
rs and op erands
op erato _
Op erators have di erent precedence levels,
+
di erent asso ciativities, and di erent arities,
e.g.,
Multiplication takes precedence over addi- UNARY
tion NODE 55 33 44
The multiplication op erator has two argu-
ments, whereas unary minus op erator has
only one
Op erands are integers, doubles, variables, INTEGER
etc. NODES
We'll just handle integers in this example:::
3 4
C Version
Expression Tree Behavior
A typical functional metho d for implement-
ing expression trees in C involves using a
struct/union to represent data structure,
Expression trees
e.g.,
{ Trees may be \evaluated" via di erent traver-
sals
typ edef struct Tree No de Tree No de;
struct Tree No de f
e.g., in-order, p ost-order, pre-order, level-
enum f
order
NUM, UNARY, BINARY
; g tag
{ The evaluation step may p erform various op-
short use ; /* reference count */
erations, e.g.,
union f
; int num
Traverse and print the expression tree
char op [2];
g o;
de ne num o.num
Return the \value" of the expression tree
de ne op o.op
union f
Generate co de
Tree No de *unary ;
struct f Tree No de *l , *r ; g binary ;
Perform semantic analysis
g c;
de ne unary c.unary
de ne binary c.binary
g;
5 6
Memory Layout of C Version
Print Tree Function
tag_
Typical C implementation cont'd
use_
Use a switch statement and a recursive func- Tree {
op_
to build and evaluate a tree, e.g., Node tion
num_ 1|2
tree Tree No de *ro ot f void print
switch ro ot->tag f
; break; case NUM: printf "d", ro ot->num UNARY:
unary_ 1 case
printf "s", ro ot->op [0];
tree ro ot->unary ; rint
Tree p
printf ""; break; BINARY:
binary_ Node case
printf "";
tree ro ot->binary .l ; print
rintf "s", ro ot->op [0];
MEMORY CLASS p
rint tree ro ot->binary .r ;
LAYOUT RELATIONSHIPS p
printf ""; break;
default:
printf "error, unknown typ e\n";
exit 1;
g
Here's what the memory layout of a struct
g
Tree No de object lo oks like
7 8
Limitations with C Approach
More Limitations with C
Problems or limitations with the typical C
approach include
Approach
{ Language feature limitations in C
The program organization makes it di-
e.g., no supp ort for inheritance and dynamic
cult to extend, e.g.,
binding
{ Any small changes will ripple through the entire
design and implementation
{ Incomplete mo deling of the application domain,
which results in
e.g., see the \ternary" extension b elow
1. Tight coupling between no des and edges in
union representation
{ Easy to make mistakes switching on typ e tags:::
2. Complexity b eing in algorithms rather than
the data structures
Solution wastes space by making worst-
case assumptions wrt structs and unions
e.g., switch statements are used to select
between various typ es of no des in the ex-
{ This is not essential, but typically o ccurs
pression trees
compare with binary search!
{ Note that this problem b ecomes worse the big-
ger the size of the largest item b ecomes!
Data structures are \passive" and func-
tions do most pro cessing work explicitly
9 10
OO Alternative
Relationships Between Trees and
Contrast previous functional approach with
an object-oriented decomp osition for the
No des
same problem:
{ Start with OO mo deling of the \expression
application domain: tree" Binary Unary Int
Node Node Node
e.g., go back to original picture
1 1
{ There are several classes involved:
class No de: base class that describ es expression
tree vertices:
No de: used for implicitly class Int
converting int to Tree no de
No de: handles unary op erators, Unary
class Ternary Node
, 10, +10, !a, or ~fo o, etc.
e.g. Node 1
No de: handles binary op erators, Binary
class 2
, a + b, 10 30, etc.
e.g. 1
Tree: \glue" co de that describ es
class Tree
ression tree edges
exp 3
{ Note, these classes mo del elements in the ap-
plication domain
i.e., no des and edges or vertices and arcs
11 12
Design Patterns in the Expression
C++ No de Interface
Tree Program
// No de.h
Adapter
NODE H if !de ned
{ \Convert the interface of a class into another
de ne NODE H
interface clients exp ect"
class Tree; // Forward decl
e.g., make Tree conform to interfaces ex-
// Describ es the Tree vertices
p ected by C++ iostreams op erators
class No de f
friend class Tree;
Factory
protected: // Only visible to derived classes
No de void
{ \Centralize the assembly of resources neces-
1 fg : use
sary to create an object"
e.g., decouple Node sub class initialization from
// pure virtual
their subsequent use
virtual void print ostream & const = 0;
// Imp ortant to make destructor virtual!
Bridge
virtual ~No de void;
{ \Decouple an abstraction from its implemen-
private:
tation so that the two can vary indep endently"
int use ; // Reference counter.
g;
e.g., printing the contents of a subtree
endif /* NODE H */
13 14
C++ Int No de and Unary No de
C++ Tree Interface
Interface
// Int No de.h
// Tree.h
include "No de.h"
include "No de.h"
class Int No de : public No de f
// Describ es the Tree edges and acts as a Factory
public:
class Tree f
Int No de int k;
public:
virtual void print ostream &stream const;
// Factory op erations
private:
Tree int;
int num ; // op erand value.
Tree const char *, Tree &;
g;
Tree const char *, Tree &, Tree &;
// Unary No de.h
Tree const Tree &t; // Copy constructor
void op erator= const Tree &t; // Assignment
include "No de.h"
~Tree void; // Destructor
class Unary No de : public No de f
void print ostream & const;
public:
Unary No de const char *op, const Tree &t;
private:
virtual void print ostream &stream const;
No de *no de ; // p ointer to a ro oted subtree.
private:
g;
const char *op eration ;
; Tree op erand
g;
15 16
Memory Layout for C++ Version
NodeNode NodeNode NodeNode PARTPART PARTPART PARTPART
node_ptr
C++ Binary No de Interface
tagtag Tree operator_operator operator_operator operator_operator
opop
No de.h // Binary
vptrvptr operand_operand left_left left_left
include "No de.h" (Tree(Tree PARTPART)) (Tree(Tree PARTPART)) (Tree(Tree PARTPART))
use_use
class Binary : public No de f
No de Node Unary Node
public:
right_
No de const char *op, ry Bina right middle_middle (Tree(Tree PARTPART))
(Tree(Tree PARTPART))
Tree &t1,
const NodeNode
Tree &t2;
const PARTPART
void print ostream &s const;
virtual Binary
rivate: p Node num_
num right_
; char *op eration const right
(Tree(Tree PARTPART))
Tree left ;
Int_Node Ternary
Tree right
; Node
g;
Memory layouts for di erent sub classes of
Node
17 18
C++ Int No de and Unary No de
Implementations
C++ Binary No de
// Int No de.C
Implementation
No de.h" include "Int
// Binary No de.C
Int No de::Int No de int k: num k f g
include "Binary No de.h"
void Int No de::print ostream &stream const f
stream << this->num ;
No de::Binary No de const char *op, Binary
g
const Tree &t1,
const Tree &t2:
op eration op, left t1, right t2
// Unary No de.C
f g
include "Unary No de.h"
void Binary No de::print ostream &stream const f
stream << "" << this->left // recursive call
Unary No de::Unary No de const char *op, const Tree &t1
<< " " << this->op eration
: op eration op, op erand t1 f g
<< " " << this->right // recursive call
<< "";
void Unary No de::print ostream &stream const f
g
stream << "" << this->op eration << " "
<< this->op erand // recursive call!
<< "";
g
19 20
The Factory Pattern
Initializing the No de Sub classes
Intent
Problem
{ \Centralize the assembly of resources neces-
{ How to ensure the No de sub classes are initial-
sary to create an object"
ized prop erly
Decouple object creation from object use by
lo calizing creation knowledge
Forces
{ There are di erent typ es of No de sub classes
This pattern resolves the following forces:
e.g., take di erent numb er and typ e of ar-
{ Decouple initialization of the Node sub classes
guments
from their subsequent use
{ We want to centralize initialization in one place
{ Makes it easier to change or add new No de
b ecause it is likely to change:::
sub classes later on
e.g., Ternary no des:::
Solution
{ Use a Factory pattern to initialize the No de
sub classes
A variant of the Factory Metho d
22 21
Structure of the Factory Pattern
the Factory Pattern
Factory Using
The Factory pattern is used by the Tree
make_product()
class to initialize No de sub classes:
ree::Tree int num
creates T
: no de new Int No de num fg
ree::Tree const char *op, const Tree &t
Product product = ... T
no de new Unary No de op, t fg
return product :
Tree::Tree const char *op,
const Tree &t1,
Tree &t2:
Product const
: no de new Binary No de op, t1, t2 fg
24 23
Printing Subtrees
The Bridge Pattern
Problem
Intent
{ How do we print subtrees without revealing
{ \Decouple an abstraction from its implemen-
their typ es?
tation so that the two can vary indep endently"
Forces
This pattern resolves the following forces
that arise when building extensible soft-
{ The Node sub class should be hidden within the
ware with C++
Tree instances
1. How to provide a stable, uniform interface that
{ We don't want to b ecome dep endent on the
is b oth closed and op en, i.e.,
use of Nodes, inheritance, and dynamic bind-
ing, etc.
{ Interface is closed to prevent direct co de
changes
{ Implementation is op en to allow extensibility
Solution
{ Use the Bridge pattern to shield the use of
2. How to simplify the implementation of operator<<
inheritance and dynamic binding
25 26
Using the Bridge Pattern
of the Bridge Pattern Structure 1: print() 1: method_impl() Tree Node Abstraction Implementor print() print() method() method_impl()
Concrete ImplementorA Int Node method_impl() Ternary Concrete Binary ImplementorB print() Node Node Unary method_impl() print() print() Node
print()
27 28
Integrating with C++ I/O
Illustrating the Bridge Pattern in
Streams
C++
Problem
The Bridge pattern is used for printing ex-
pression trees:
{ Our Tree interface uses a print metho d, but
most C++ programmers exp ect to use I/O
Streams
void Tree::print ostream &os const
f
this->no de ->print os;
g
Forces
Note how this pattern decouples the Tree
{ Want to integrate our existing C++ Tree class
interface for printing from the Node sub-
into the I/O Stream paradigm without mo di-
class implementation
fying our class or C++ I/O
{ i.e., the Tree interface is xed, whereas the
Node implementation varies
Solution
{ However, clients need not be concerned ab out
{ Use the Adapter pattern to integrate Tree with
the variation:::
I/O Streams
30 29
Structure of the Adapter Pattern
The Adapter Pattern
1: request ()
Intent
Target
Convert the interface of a class into another
{ client
client exp ects
interface request()
Adapter lets classes work together that couldn't
otherwise b ecause of incompatible interfaces
This pattern resolves the following force:
Adapter Adaptee
How to transparently integrate the Tree with
1. request() 2: specific_request() specific_request()
the C++ iostream op erators
31 32
Using the Adapter Pattern
Using the Adapter Pattern
1: operator<<
The Adapter pattern is used to integrate
C++ I/O Streams
client Target with
&op erator<< ostream &s, const Tree &tree f
operator<< ostream
tree.print s;
// This triggers No de * virtual call via
// tree.no de ->print s, which is
// implemented as the following:
->vptr[1] tree.no de , s; // *tree.no de
return s;
Adapter Adaptee g
Note how the C++ co de shown ab ove operator<< print()
2: print()
uses I/O streams to \adapt" the Tree interface:: :
34 33
C++ Main Program
C++ Tree Implementation
// main.C
Reference counting via the \counted b o dy"
idiom
include
include "Tree.h"
t.no de f Tree::Tree const Tree &t: no de
// Sharing, ref-counting.
int main void
this->no de ->use ++;
f
g
const Tree t1 = Tree "*",
Tree "-", 5,
void Tree::op erator= const Tree &t f
Tree "+", 3, 4;
// order imp ortant here!
// Tree "*",
t.no de ->use ++;
// Tree "-", Tree 5,
this->no de ->use --;
// Tree "+", Tree 3, Tree 4;
if this->no de ->use == 0
delete this->no de ;
// prints 5 * 3 + 4.
this->no de = t.no de ;
cout << t1 << endl;
g
const Tree t2 = Tree "*", t1, t1;
Tree::~Tree void f
// prints 5 * 3 + 4 * 5 * 3 + 4.
// Ref-counting, garbage collection
cout << t2 << endl;
this->no de ->use --;
->use <= 0 if this->no de
// Destructors of t1 and t2 recursively
delete this->no de ;
// delete entire tree leaving scop e.
g
g
36 35
Expression Tree Diagram 2
Expression Tree Diagram 1 t2 t1 * Binary Node * print() Binary t1 Node * print() Unary - + Node Unary Node - + Int Node
5 4 Int 3 Node 4 5
3
Expression tree for t1 = 5 * 3 + 4
Expression tree for t2 = t1 * t1
37 38
C++ Ternary No de
Adding Ternary No des
Implementation
// Ternary No de.C
Extending the existing program to supp ort
ternary no des is straightforward
No de.h" include "Ternary
{ i.e., just derived new class Ternary No de
Ternary No de::Ternary No de const char *op,
const Tree &a,
const Tree &b,
class Ternary No de: handles ternary
const Tree &c
op erators, e.g., a == b ? c : d, etc.
: op eration op, left a, middle b,
right c fg
No de.h // Ternary
void Ternary No de::print ostream &stream const f
stream << this->op eration << ""
include "No de.h"
<< this->left // recursive call
<< "," << this->middle // recursive call
<< "," << this->right // recursive call
class Ternary No de : public No de f
<< "";
public:
g
Ternary No de const char *, const Tree &,
const Tree &, const Tree &;
// Mo di ed class Tree Factory
virtual void print ostream & const;
private:
class Tree f // add 1 class constructor
const char *op eration ;
public:
Tree left , middle , right ;
Tree const char *, const Tree &,
g;
const Tree &, const Tree &
: no de new Ternary No de op, l, m, r fg
// Same as b efore:: :
39 40
Di erences from C
Implementation
Summary of Expression Tree
Example
On the other hand, mo difying the original
C approach requires changing:
{ The original data structures, e.g.,
OO version represents a more complete
mo deling of the application domain
No de f struct Tree
enum f
{ e.g., splits data structures into mo dules that NUM, UNARY, BINARY, TERNARY
g tag ;
corresp ond to \objects" and relations in ex-
// same as b efore
pression trees
union f
// same as b efore
// add this
struct f
Tree No de *l , *m , *r ;
g ternary ;
Use of C++ language features simpli es
g c;
the design and facilitates extensibility
c.ternary de ne ternary
g;
{ and many parts of the co de, e.g.,
{ e.g., the original source was hardly a ected
void print tree Tree No de *ro ot f
// same as b efore
case TERNARY: // must be TERNARY.
printf "";
print tree ro ot->ternary .l ;
Use of patterns helps to motivate and jus-
printf "c", ro ot->op [0];
print tree ro ot->ternary .m ;
tify design choices
printf "c", ro ot->op [1];
print tree ro ot->ternary .r ;
printf ""; break;
// same as b efore
g
42 41
Potential Problems with OO
Design
Case Study 2: System Sort
Solution is very \data structure rich"
Develop a general-purp ose system sort
{ e.g., requires con guration management to han-
{ It sorts lines of text from standard input and
dle many headers and .C les!
writes the result to standard output
{ e.g., the UNIX system sort
May be somewhat less ecient than orig-
inal C approach
sort < big. le > sorted. le
{ e.g., due to virtual function overhead
In the following, we'll examine the primary
forces that shap e the design of this appli-
In general, however, virtual functions may
cation
be no less inecient than large switch
statements or if/else chains:::
For each force, we'll examine patterns that
As a rule, be careful of micro vs. macro
resolve it
optimizations
{ i.e., always pro le your co de!
44 43
External Behavior of System Sort
High-level Forces
Solution should be b oth time and space
A \line" is a sequence of characters ter-
ecient
minated by a newline
{ e.g., must use appropriate algorithms and data
structures
Default ordering is lexicographic by bytes
in machine collating sequence
{ Ecient I/O and memory management are par-
ticularly imp ortant
The ordering is a ected globally by the
{ Our solution uses minimal dynamic binding to
following options:
avoid unnecessary overhead
{ Ignore case -i
{ Sort numerically -n
Solution should leverage reusable comp o-
nents
{ Sort in reverse -r
{ Begin sorting at a sp eci ed eld -f
{ e.g., iostreams, Array and Stack classes, etc.
{ Begin sorting at a sp eci ed column -c
Solution should yield reusable comp onents
Note, our program need not sort les larger
{ e.g., ecient input classes, generic sort rou-
tines, etc.
than main memory
45 46
General Form of Solution
General OOD Solution Approach
Identify the \objects" in the application
Note the use of existing C++ mechanisms
and solution space
like I/O streams
{ e.g., stack, array, input class, options, access
table, sorts, etc.
// Reusable function
template
sort ARRAY &a;
int main int argc, char *argv[]
Recognize and apply common design pat-
{
terns
parse_args argc, argv;
{ e.g., Singleton, Factory, Adapter, Iterator
Input_Array input;
cin >> input;
Implement a framework to co ordinate com-
sort input;
p onents
cout << input;
{ e.g., use C++ classes and parameterized typ es
}
48 47
C++ Class Mo del
C++ Class Comp onents Options System
Sort
Tactical comp onents GLOBAL
Sort_AT Stack
Adapter {
Used by non-recursive quick sort Sort STRATEGIC Line_Ptrs
COMPONENTS
Array Sort_AT {
Adapter
Stores p ointers to lines and elds
ARRAY
Table Access Sort {
TYPE
Used to store and sort input TACTICAL Access
COMPONENTS Table
{ Input TYPE TYPE
Input
Eciently reads arbitrary sized input using
Array
1 dynamic allo cation and 1 copy
Stack only
50 49
Detailed Format for Solution
C++ Class Comp onents
Strategic comp onents
Note the separation of concerns
{ System Sort
// Prototypes
template
Integrates everything:::
void operator >> istream &,
Access_Table
void operator << ostream &,
{ Sort AT Adapter
const Access_Table
Table Integrates the Array and the Access
int main int argc, char *argv[]
{
{ Options
Options::instance ->parse_args argc, argv;
Manages globally visible options
cin >> System_Sort::instance ->access_table ;
sort System_Sort::instance ->access_table ;
{ Sort
cout << System_Sort::instance ->access_table ;
e.g., b oth quicksort and insertion sort
}
52 51
Reading Input Eciently
Access Table Format
ACCESS BUFFER
Problem
{ The input to the system sort can be arbitrarily
large e.g., up to size of main memory
Forces
{ To improve p erformance solution must mini-
mize:
1. Data copying and data manipulation
ACCESS ARRAY
2. Dynamic memory allo cation
Solution
{ Create an Input class that reads arbitrary in-
put eciently
53 54
The Input Class
Design Patterns in System Sort
Eciently reads arbitrary-sized input us-
Facade
ing only 1 dynamic allo cation
{ \Provide a uni ed interface to a set of inter-
faces in a subsystem"
class Input
{
Facade de nes a higher-level interface that
public:
makes the subsystem easier to use
// Reads from up to
// replacing
// pointer to dynamically allocated buffer.
{ e.g., sort provides a facade for the complex
char *read istream &input,
internal details of ecient sorting
int terminator = EOF,
int search = '\n',
int replace = '\0';
// Number of bytes replaced.
Adapter
size_t replaced void const;
{ \Convert the interface of a class into another
// Size of buffer.
interface clients exp ect"
size_t size void const;
Adapter lets classes work together that couldn't
private:
otherwise b ecause of incompatible interfaces
// Recursive helper method.
char *recursive_read void;
{ e.g., make Access Table conform to inter-
// ...
faces exp ected by sort and iostreams
};
55 56
Design Patterns in System Sort Design Patterns in System Sort
cont'd cont'd
Singleton
Factory
{ \Ensure a class has only one instance, and pro-
{ \Centralize the assembly of resources neces-
vide a global p oint of access to it"
sary to create an object"
{ e.g., provides a single p oint of access for sys-
Ptrs used { e.g., decouple initialization of Line
tem sort and for program options
by Access Table from their subsequent use
Double-Checked Lo cking Optimization
Bridge
{ \Ensures atomic initialization or access to ob-
jects and eliminates unnecessary lo cking over-
{ \Decouple an abstraction from its implemen-
head"
tation so that the two can vary indep endently"
{ e.g., allows multiple threads to execute sort
{ e.g., comparing two lines to determine ordering
Iterator
Strategy
{ \Provide a way to access the elements of an
aggregate object sequentially without exp osing
{ \De ne a family of algorithms, encapsulate each
its underlying representation"
one, and make them interchangeable"
{ e.g., provides a way to print out the sorted lines
without exp osing representation { e.g., allow exible pivot selection
58 57
Sort Algorithm
Quicksort Optimizations
For eciency, two typ es of sorting algo-
rithms are used:
1. Non-recursive
1. Quicksort
Uses an explicit stack to reduce function call
{ Highly time and space ecient sorting arbi-
overhead
trary data
{ On log n average-case time complexity
2. Median of 3 pivot selection
{ On2 worst-case time complexity
Reduces probability of worse-case time com-
plexity
{ Olog n space complexity
{ Optimizations are used to avoid worst-case
b ehavior
3. Guaranteed log n space complexity
2. Insertion sort
Always \pushes" larger partition
{ Highly time and space ecient for sorting
\almost ordered" data
4. Insertion sort for small partitions
{ On2 average- and worst-case time com-
plexity
Insertion sort runs fast on almost sorted data
{ O1 space complexity
60 59
Selecting a Pivot Value
The Strategy Pattern
Problem
Intent
{ There are various algorithms for selecting a
{ De ne a family of algorithms, encapsulate each
pivot value
one, and make them interchangeable
e.g., randomization, median of three, etc.
Strategy lets the algorithm vary indep endently
from clients that use it
Forces
This pattern resolves the following forces
{ Di erent input may sort more eciently using
di erent pivot selection algorithms
1. How to extend the p olicies for selecting a pivot
value without mo difying the main quicksort al-
gorithm
Solution
2. Provide a one size ts all interface without
{ Use the Strategy pattern to select the pivot
forcing a one size ts all implementation
selection algorithm
62 61
Structure of the Strategy Pattern
STRATEGY Strategy
the Strategy Pattern Context Using context_interface() algorithm_interface() quick_sort Pivot Strategy get_pivot()
Concrete Concrete pivot_strat->get_pivot (array, lo, hi) Strategy A Strategy C Median of algorithm_interface() algorithm_interface() Select Three First Random Concrete Strategy B
algorithm_interface()
63 64
Devising a Simple Sort Interface
Implementing the Strategy
Problem
Pattern
{ Although the implementation of the sort func-
tion is complex, the interface should be simple
to use
ARRAY is the particular \context"
template
Key forces
void sort ARRAY &array
{
{ Complex interface are hard to use, error prone,
Pivot
and discourage extensibility and reuse
Options::instance ->pivot_strat ;
quick_sort array, pivot_strat;
{ Conceptually, sorting only makes a few assump-
}
tions ab out the \array" it sorts
template
e.g., supp orts operator[] metho ds, size,
quick_sort ARRAY &array, PIVOT_STRAT *pivot_strat
and element TYPE
{
for ;; {
{ We don't want to arbitrarily limit typ es of ar-
ARRAY::TYPE pivot; // typename ARRAY::TYPE pivot...
rays we can sort
pivot = pivot_strat->get_pivot array, lo, hi;
// Partition array[lo, hi] relative to pivot...
Solution
}
}
{ Use the Facade and Adapter patterns to sim-
plify the sort program
66 65
Structure of the Facade Pattern
Facade Pattern
EXTERNALLY Intent
VISIBLE
Provide a uni ed interface to a set of interfaces
{ Facade
in a subsystem
HIDDEN
Facade de nes a higher-level interface that
makes the subsystem easier to use
This pattern resolves the following forces:
1. Simpli es the sort interface
{ e.g., only need to supp ort operator[] and
size metho ds, and element TYPE
2. Allows the implementation to be ecient and
arbitrarily complex without a ecting clients
67 68
Using the Facade Pattern
Adapter Pattern EXTERNALLY The ARRAY
VISIBLE Intent
Sort
{ \Convert the interface of a class into another
clients exp ect"
HIDDEN interface
Adapter lets classes work together that couldn't
b ecause of incompatible interfaces ARRAY otherwise
Quick
This pattern resolves the following forces: Sort
TYPE
1. How to transparently integrate the Access Table
the sort routine Stack with
ARRAY
Table 2. How to transparently integrate the Access
the C++ iostream op erators Insert with
Sort
70 69
Using the Adapter Pattern
"conforms to"
of the Adapter Pattern Structure ARRAY ARRAY 1: request () sort ARRAY::ARRAY::TYPETYPE operator[] size() client Target request() 1: ARRAY::TYPE t = array[i] "conforms to"
Line_Ptrs Adapter Adaptee Sort_AT_Adapter TYPE typedef Line_Ptrs TYPE request() specific_request() Access_Table 2: specific_request() make_table() operator[] make_table() size() length()
element()
71 72
The Access Table Class
Dynamic Array
Eciently maps indices onto elements in
the data bu er
De nes a variable-sized array for use by
template
class Access_Table
Table the Access
{
public:
// Factory Method for initializing Access_Table.
template
virtual int make_table size_t num_lines,
class Array
char *buffer = 0;
{
// Release buffer memory.
public:
virtual ~Access_Table void { delete [] buffer_; }
typedef T TYPE; // Type "trait"
// Retrieve reference to
Array size_t size = 0;
T &element size_t index {
int init size_t size;
return access_array_[index];
T &operator[]size_t index;
}
size_t size void const;
// ...
// Length of the access_array.
size_t length void const {
private:
return access_array_.size ;
T *array_;
}
size_t size_;
protected:
};
Array
char *buffer_; // Hold the data buffer.
};
74 73
The Sort AT Adapter Class
Centralizing Option Pro cessing
Adapts the Access Table to conform to
the ARRAY interface exp ected by sort
Problem
struct Line_Ptrs {
{ Command-line options must be global to many
// Comparison operator used by sort.
parts of the sort program
int operator< const Line_Ptrs &;
// Beginning of line and field/column.
char *bol_, *bof_;
Key forces
};
{ Unrestricted use of global variables increases
class Sort_AT_Adapter :
system coupling and can violate encapsulation
// Note the use of the "Class form" of the Adapter
private Access_Table
public:
{ Initialization of static objects in C++ can be
virtual int make_table size_t num_lines, char *buffer;
problematic
typedef Line_Ptrs TYPE; // Type "trait".
// These methods adapt Access_Table methods...
Solution
T &operator[] size_t index {
return element index;
{ Use the Singleton pattern to centralize option
}
pro cessing
size_t size void const { return length ; }
};
76 75
Singleton Pattern
Structure of the Singleton Pattern Intent
if (unique_instance_ == 0)
\Ensure a class has only one instance, and pro-
{ unique_instance_ = new Singleton;
a global p oint of access to it"
vide return unique_instance_;
This pattern resolves the following forces:
Lo calizes the creation and use of \global" vari-
1. Singleton
ables to well-de ned objects
static instance()
Preserves encapsulation 2. singleton_operation()
get_singleton_data()
3. Ensures initialization is done after program has
rted and only on rst use sta static unique_instance_
singleton_data_
4. Allow transparent sub classing of Singleton im-
plementation
77 78
Options Class
This manages globally visible options
Using the Singleton Pattern
class Options
if (unique_instance_ == 0) { public:
Options *instance void;
unique_instance_ = new Options; static
parse_args int argc, char *argv[];
return unique_instance_; void
// These options are stored in octal order
// so that we can use them as bitmasks!
enum Option { FOLD = 01, NUMERIC = 02,
= 04, NORMAL = 010 };
Options REVERSE
enum Pivot_Strategy { MEDIAN, RANDOM, FIRST };
static instance()
enabled Option o;
bool enabled() bool
field_offset void; // Offset from BOL.
field_offset() int
pivot_strat void;
static unique_instance_ Pivot_Strategy
*compare const char *l, const char *r;
options_ int
protected:
Options void; // Ensure Singleton.
u_long options_; // Maintains options bitmask...
int field_offset_;
static Options *instance_; // Singleton.
};
80 79
Eciently Avoiding Race
Conditions for Singleton
Using the Options Class
Initialization
Problem
The following is the comparison op erator
used by sort
{ A multi-threaded program might have execute
multiple copies of sort in di erent threads
int
Line_Ptrs::operator< const Line_Ptrs &rhs
{
Key forces
Options *options = Options::instance ;
if options->enabled Options::NORMAL
{ Subtle race conditions can cause Singletons to
return strcmp this->bof_, rhs.bof_ < 0;
be created multiple times
else if options->enabled Options::FOLD
{ Lo cking every access to a Singleton can be to o
return strcasecmp this->bof_, rhs.bof_ < 0;
costly
else
// assert options->enabled Options::NUMERIC;
return numcmp this->bof_, rhs.bof_ < 0;
Solution
}
{ Use the Double-Checked Lo cking Optimiza-
tion pattern to ecently avoid race conditions
when initialization Singletons
82 81
Structure of the Double-Checked
The Double-Checked Lo cking
Lo cking Optimization Pattern
Optimization Pattern
if (unique_instance_ == NULL) { Intent mutex_.acquire ();
if (unique_instance_ == NULL)
Ensures atomic initialization or access to ob-
{ unique_instance_ = new Singleton;
and eliminates unnecessary lo cking over- jects mutex_.release ();
head }
return unique_instance_;
This pattern resolves the following forces:
Ensures atomic initialization or access to ob-
1. Singleton
regardless of thread scheduling order jects, static instance()
static unique_instance_
2. Keeps lo cking overhead to a minimum
e.g., only lo ck on rst access, rather than for
{ Mutex
the entire Singleton instance metho d
83 84
Using the Double-Checked
Lo cking Optimization Pattern
Simplifying Comparisons
Uses the Adapter pattern to turn ordi-
Problem
nary classes into Singletons optimized au-
{ The comparison op erator shown ab ove is some-
tomatically with the Double-Checked Lo ck-
what complex
ing Optimization pattern
template
Forces
class Singleton {
public:
{ It's b etter to determine the typ e of comparison
static TYPE *instance void;
op eration during the initialization phase
protected:
static TYPE *instance_;
{ But the interface shouldn't change
static LOCK lock_;
};
template
Singleton
Solution
// Perform the Double-Check.
if instance_ == 0 {
{ Use the Bridge pattern to separate interface
Guard
from implementation
if instance_ == 0 instance_ = new TYPE;
}
return instance_;
}
86 85
The Bridge Pattern
Structure of the Bridge Pattern
Intent
1: method_impl()
{ \Decouple an abstraction from its implemen-
so that the two can vary indep endently" tation Abstraction Implementor
method() method_impl()
This pattern resolves the following forces
that arise when building extensible soft-
ware
Concrete
1. How to provide a stable, uniform interface that
b oth closed and op en, i.e., is ImplementorA
method_impl()
Closed to prevent direct co de changes { Concrete
ImplementorB
Op en to allow extensibility
{ method_impl()
2. How to simplify the Line Ptrs::op erator< im-
plementation
87 88
Using the Bridge Pattern
Using the Bridge Pattern
The following is the comparison op erator
by sort 1: compare() used Options
Line_Ptrs int
Line_Ptrs &rhs
operator< compare() Line_Ptrs::operator<const
{
return *Options::instance ->compare
bof_, rhs.bof_; }
strcmp()
This solution is much more concise strcasecmp()
numcmp()
However, there's an extra level of function
call indirection:::
{ Which is equivalent to a virtual function call
90 89
Initializing the Comparison
Op erator
The Factory Pattern
Problem
Intent
{ How do es the compare p ointer-to-metho d get
assigned?
{ \Centralize the assembly of resources neces-
sary to create an object"
int *compare const char *left, const char *right;
Decouple object creation from object use by
Forces
lo calizing creation knowledge
{ There are many di erent choices for compare,
dep ending on which options are enabled
This pattern resolves the following forces:
{ We only want to worry ab out initialization de-
tails in one place
{ Decouple initialization of the compare op era-
{ Initialization details may change over time
tor from its subsequent use
{ We'd like to do as much work up front to re-
{ Makes it easier to change comparison p olicies
duce overhead later on
later on
e.g., adding new command-line options
Solution
{ Use a Factory pattern to initialize the compar-
ison op erator
92 91
Using of the Factory Pattern for
Structure of the Factory Pattern
Comparisons Factory Options make_product() parse_args()
creates
creates Product product = ... return product initialize compare
Compare
Product Function
93 94
Co de for Using the Factory
Initializing the Access Table
Pattern
Problem
The following initialization is done after
{ One of the nastiest parts of the whole system
sort program is initializing the Access Table
command-line options are parsed
Options::parse_args int argc, char *argv[]
{
Key forces
Options *options = Options::instance ;
// ...
{ We don't want initialization details to a ect
if options->enabled Options::NORMAL
subsequent pro cessing
options->compare = &strcmp;
else if options->enabled Options::FOLD
{ Makes it easier to change initialization p olicies
options->compare = &strcasecmp;
later on
else if options->enabled Options::NUMERIC
options->compare = &numcmp;
// ...
e.g., using the Access Table in non-sort ap-
plications
int numcmp const char *s1, const char * s2
{
double d1 = strtod s1, 0, d2 = strtod s2, 0;
Solution
if d1 < d2 return -1;
else if d1 > d2 return 1;
{ Use the Factory Metho d pattern to initialize
else // if d1 == d2
the Access Table
return 0;
}
95 96
Factory Metho d Pattern
Structure of the Factory Metho d
Intent
Pattern
{ De ne an interface for creating an object, but
sub classes decide which class to instantiate let Creator
factory_method() = 0
Factory Metho d lets a class defer instantia-
make_product()
tion to sub classes
Product Product *product = factory_method()
return product
This pattern resolves the following forces:
{ Decouple initialization of the Access
Table Concrete
its subsequent use from Creator
factory_method()
{ Improves subsequent p erformance by pre-caching
of each eld and line b eginning Concrete Product CREATES
return new Concrete_Product
{ Makes it easier to change initialization p olicies
later on
e.g., adding new command-line options
98 97
Using the Factory Metho d
Using the Factory Metho d
Pattern for Access Table
Pattern for the Sort AT Adapter Initialization
TYPE
The following iostream Adapter initializes
Access Table
AT Adapter access table the Sort
make_table() = 0
void operator>> istream &is,
Sort_AT_Adapter &access_table
{
Input input;
Line Ptrs
Read entire stdin into buffer.
Sort AT //
*buffer = input.read is;
Adapter char
// Determine number of lines.
num_lines = input.replaced ;
make_table() size_t
// Factory Method initializes Access_Table
access_table.make_table num_lines, buffer;
// initialize the table }
100 99
Implementing the Factory Pattern
Initializing the Access Table with
Table Factory class has a Fac- The Access
Input Bu er
tory Metho d that initializes Sort AT Adapter
Problem
// Factory Method initializes Access_Table.
int Sort_AT_Adapter::make_table size_t num_lines,
char *buffer
Table without { We'd like to initialize the Access
{
having to know the input bu er is represented
// Array assignment op.
this->access_array_.resize num_lines;
this->buffer_ = buffer; // Obtain ownership.
Key force
size_t count = 0;
{ Representation details can often be decoupled
// Iterate through the buffer and determine
from accessing each item in a container or col-
// where the beginning of lines and fields
lection
// must go.
for Line_Ptrs_Iter iter buffer, num_lines;
iter.is_done == 0;
Solution
iter.next
{
{ Use the Iterator pattern to scan through the
Line_Ptrs line_ptr = iter.current_element ;
bu er
this->access_array_[count++] = line_ptr;
}
}
102 101
Iterator Pattern
Iterator Pattern cont'd
Intent
{ Provide a way to access the elements of an
aggregate object sequentially without exp osing
The Iterator pattern also provides a way to
its underlying representation
print out the sorted lines without exp osing
representation
The Iterator pattern provides a way to ini-
void operator<< ostream &os,
tialize the Access Table without knowing
const Sort_AT_Adapter &at
{
how the bu er is represented:
if Options::instance ->enabled Options::REVERSE
for size_t i = at.size ; i > 0; i--
Line_Ptrs_Iter::Line_Ptrs_Iter
os << at[i - 1].bol_;
char *buffer, size_t num_lines;
else
Line_Ptrs
for size_t i = 0; i < at.size ; i++
Line_Ptrs_Iter::current_element void
os << at[i].bol_;
{
}
Line_Ptrs lp;
// Determine beginning of next line and next field...
lp.bol_ = // .....
Note that STL is heavily based on itera-
lp.bof_ = // .....
tors
return lp;
}
104 103
Case Study 3: Sort Veri er
Summary of System Sort Case
Study
Verify whether a sort routine works cor-
rectly
This case study illustrates using OO tech-
{ i.e., output of the sort routine must be an or-
dered p ermutation of the original input
niques to structure a mo dular, reusable,
and highly ecient system
This is useful for checking our system sort
routine!
Design patterns help to resolve many key
{ The solution is harder than it lo oks at rst
forces
glance:::
Performance of our system sort is compa-
rable to existing UNIX system sort
As b efore, we'll examine the key forces
{ Use of C++ features like parameterized typ es
and discuss design patterns that resolve
and inlining minimizes p enalty from increased
mo dularity, abstraction, and extensibility
the forces
106 105
General Form of Solution
The following is a general use-case for this
Common Problems
routine:
template
unsorted
sort ARRAY &a;
sorted, but
0 0 0 0 0 0 0 0
not p ermuted
template
p ermuted, but
8 13 18 15 4 13 4 7
not sorted
check_sort const ARRAY &o, const ARRAY &p;
sorted and
4 4 7 8 13 13 15 18
p ermuted
int main int argc, char *argv[]
{
Options::instance ->parse_args argc, argv;
Several common problems:
Input_Array input;
{ Sort routine may zero out data
Input_Array potential_sort;
cin >> input;
though it will app ear sorted:::;-
copy input, potential_sort;
{ Sort routine may fail to sort data
sort potential_sort;
if check_sort input, potential_sort == -1
{ Sort routine may erroneously add new values
cerr << "sort failed" << endl;
else
cout << "sort worked" << endl;
}
107 108
Forces
Forces cont'd
Solution should be b oth time and space
Multiple implementations will be neces-
ecient
sary, dep ending on prop erties of the data
b eing examined, e.g.,
{ e.g., it should not take more time to check
than to sort in the rst place!
1. if data values are small in relation to numb er
of items and integrals use :::
{ Also, this routine may be run many times con-
secutively, which may faciliate certain space
2. if data has no duplicate values use :::
optimizations
3. if data has duplicate values use :::
We cannot assume the existence of a \cor-
rect" sorting algorithm:: :
This problem illustrates a simple example
{ Therefore, to improve the chance that our so-
of \program families"
lution is correct, it must be simpler than writ-
ing a correct sorting routine
{ i.e., we want to reuse as much co de and/or
design across multiple solutions as p ossible
Quis costo diet ipsos custo des?
109 110
Strategies
General OOD Solution Approach
Implementations of search structure vary
according to data, e.g.,
Identify the \objects" in the application
and solution space
1. Range Vector
{ e.g., use a search structure ADT organiza-
{ ON time complexity and space ecient for
tion with memb er function such as insert and
sorting \small" ranges of integral values
remove
2. Binary Search version 1
{ On log n time complexity and space e-
Recognize common design patterns
cient but do es not handle duplicates
{ e.g., Strategy, Template Metho d, and Factory
Metho d
3. Binary Search version 2
{ On log n time complexity, but handles du-
plicates
Implement a framework to co ordinate mul-
tiple implementations
4. Hashing
{ e.g., use classes, parameterized typ es, inheri-
{ On b est/average case, but On2 worst
tance and dynamic binding
case, handles duplicates, but p otentially not
as space ecient
111 112
General OOD solution approach
High-level Algorithm
cont'd
e.g., pseudo co de
C++ framework should be amenable to:
template
int check sort const ARRAY &original,
{ Extension and Contraction
sort const ARRAY &p otential
f
May discover b etter implementations
Perform basic sanity check to see if the
potential sort is actually in order
May need to conform to resource constraints
can also detect duplicates here
May need to work on multiple typ es of data
if basic sanity check succeeds then
Initialize search structure srchstrct
{ Performance Enhancement
for i 0 to size 1 lo op
insert potential sort[i]
May discover b etter ways to allo cate and
into srchstrct
cache memory
for i 0 to size 1 lo op
if remove original[i] from
srchstrct fails then
Note, improvements should be transparent
return ERROR
to existing co de:: :
return SUCCESS
else
{ Portability
return ERROR
end if
May need to run on multiple platforms
g
113 114
C++ Class Interfaces
C++ Class Mo del
Search structure base class.
TYPE template
class Search_Struct {
Search public:
int insert const T &new_item = 0;
Struct virtual
virtual int remove const T &existing_item = 0;
~Search_Struct void = 0;
LONG TYPE virtual }; Range Hash
Vector Table
Strategy Factory class
TYPE
Search public:
Singleton method. Search //
Nodups
Search_Strategy *instance void;
Dups static
// Factory Method
virtual Search_Struct
make_strategy const ARRAY &;
};
116 115
C++ Class Interfaces cont'd
Strategy sub classes
Design Patterns in Sort Veri er
// Note the template specialization
class Range_Vector : public Search_Struct
Factory Metho d
{ typedef long TYPE; /* ... */ };
{ \De ne an interface for creating an object, but
template
let sub classes decide which class to instanti-
class Binary_Search_Nodups : public Search_Struct ate" { typedef T TYPE; /* ... */ Factory Metho d lets a class defer instantia- }; tion to sub classes template class Binary_Search_Dups : public Search_Struct { typedef T TYPE; /* ... */ In addition, the Facade, Iterator, Single- }; ton, and Strategy patterns are used template class Hash_Table : public Search_Struct { typedef T TYPE; /* ... */ }; 117 118 Using the Strategy Pattern TYPE Search Factory Metho d Pattern check_sort The Struct Strategy Intent TYPE { De ne an interface for creating an object, but sub classes decide which class to instantiate long Hash let Table TYPE Factory Metho d lets a class defer instantia- Range TYPE to sub classes Vector Binary Binary tion Search Search Dups Nodups This pattern resolves the following force: 1. How to extend the initialization strategy in the sort veri er transparently This pattern extends the strategies for check- ing if an array is sorted without mo difying the check sort algorithm 120 119 Structure of the Factory Metho d Using the Factory Metho d Pattern Pattern Creator Search factory_method() = 0 Strategy make_product() Search make_strategy() Struct Product Product *product = factory_method() return product Concrete New Search Creator Strategy factory_method() make_strategy() New Search Concrete Struct Product CREATES CREATES return new Concrete_Product return new New_Search_Struct 121 122 Implementing the check sort Function Initializing the Search Structure e.g., pseudo-co de for the sort veri cation template Search_Strategy strategy const ARRAY &potential_sort { template int duplicates = 0; check_sort const ARRAY &orig, const ARRAY &p_sort { for size_t i = 1; i < size; i++ if orig.size != p_sort.size if potential_sort[i] < potential_sort[i - 1] return -1; return 0; else if potential_sort[i] == potential_sort[i - 1] auto_ptr < Search_Struct duplicates++; Search_Strategy p_sort; if duplicates == 0 return new Binary_Search_Nodups for int i = 0; i < p_sort.size ; i++ potential_sort; if ss->insert p_sort[i] == -1 else if size 2 return -1; return new Binary_Search_Dups potential_sort, duplicates for int i = 0; i < orig.size ; i++ else return new Hash_Table if ss->remove orig[i] == -1 size, &hash_function; return -1; } return 0; // auto_ptr's destructor deletes the memory... } 123 124 Sp ecializing the Search Structure for Range Vectors Summary of Sort Veri er Case template Search_Strategy Study const Array { int duplicates = 0; The sort veri er illustrates how to use OO techniques to structure a mo dular, exten- for size_t i = 1; i < size; i++ sible, and ecient solution if potential_sort[i] < potential_sort[i - 1] return 0; { The main pro cessing algorithm is simpli ed else if potential_sort[i] == potential_sort[i - 1] duplicates++; { The complexity is pushed into the strategy ob- long range = potential_sort[size - 1] - jects and the strategy selection factory potential_sort[0]; if range <= size { Adding new solutions do es not a ect existing return new Range_Vector potential_sort[0], co de potential_sort[size - 1] else if duplicates == 0 return new Binary_Search_Nodups { The appropriate ADT search structure is se- potential_sort; lected at run-time based on the Strategy pat- else if size 2 tern return new Binary_Search_Dups potential_sort, duplicates else return new Hash_Table size, &hash_function; } 126 125