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, , Singleton,

[email protected]

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

 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 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 void

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 void sort ARRAY &a;

 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 with . Returns

// 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

 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 *pivot_strat = Pivot::make_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

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 element.

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 access_array_; // Access table is array of T.

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 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 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_;

};

template TYPE *

Singleton::instance void {

 Solution

// Perform the Double-Check.

if instance_ == 0 {

{ Use the Bridge pattern to separate interface

Guard mon lock_;

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 void 7 13 4 15 18 13 8 4

unsorted

sort ARRAY &a;

sorted, but

0 0 0 0 0 0 0 0

not p ermuted

template int

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

TYPE template Binary Search_Strategy Binary {

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_Struct *

Search_Strategy::make_strateg y

strategy

const ARRAY &potential_sort

{

template int

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 > ss =

duplicates++;

Search_Strategy::instance ->make_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_Struct *

Search_Strategy >::make_strategy

Study

const Array &potential_sort

{

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