Design Pattern Case Studies with C++ Douglas C. Schmidt Http
Total Page:16
File Type:pdf, Size:1020Kb
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, [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 op erators and op erands _ 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.