140 likes | 147 Views
Review: Design Pattern Structure. A design pattern has a name So when someone says “Adapter” you know what they mean So you can communicate design ideas as a “vocabulary” A design pattern describes the core of a solution to a recurring design problem
E N D
Review: Design Pattern Structure • A design pattern has a name • So when someone says “Adapter” you know what they mean • So you can communicate design ideas as a “vocabulary” • A design pattern describes the core of a solution to a recurring design problem • So you don’t have to reinvent known design techniques • So you can benefit from others’ (and your) prior experience • A design pattern is capable of generating many distinct design decisions in different circumstances • So you can apply the pattern repeatedly as appropriate • So you can work through different design problems using it
Three More Design Patterns • Singleton (creational) • Provides access to a single (or indexed) instance • Prototype (creational) • Polymorphic duplication of heterogeneous types • Visitor (behavioral) • Allows interaction with heterogeneous collections • Compile time dispatching is standard, but can emulate with dynamic type casting or RTTI
Creational Patterns • Help define how objects are created/initialized • May emphasize class or interaction diagrams • Examples • Factory Method • Singleton • Prototype
Single Global Instance • Challenges • Only one object of a type is needed or allowed in a program • Whether or not the object is needed at all may vary • Need to make sure the object is initialized before first use • Motivates use of the Singleton pattern • Instantiates the object on-demand (if requested) • If no request is made, the object is never created (efficient) • Makes it easy to obtain an alias from anywhere in program • Don’t need to pass a reference/pointer up and down the call stack • Initializes object before first alias to it is handed out • Lifetime of the object covers interval of its possible use
Singleton Pattern • Problem • Want to ensure a single instance of a class, that’s shared by all uses throughout a program (e.g., the Portfolio, the Zoo) • Context • Need to address ordering of initialization versus usage • E.g., usage from different source files than where the object is defined • Solution core • Static pointer member variable is initialized to 0 (before main starts) • Provide a global access method (static member function) • First use of the access method instantiates object, updates pointer • Constructors for instance can be hidden (made private) • Can hide destructor too if a “fini” or “destroy” method is also provided • Consequences • Object is never created if it’s never used • Object is shared efficiently among all uses
Basic Use of the Singleton Pattern class Portfolio { public: static Portfolio * instance(); static void fini(); // . . . private: static Portfolio * instance_; Portfolio (); virtual ~Portfolio (); // . . . }; Portfolio * Portfolio::instance_ = 0; Portfolio * Portfolio::instance() { if (instance_ == 0){ instance_ = new Portfolio; } return instance_; } void Portfolio::fini() { delete instance_; instance_ = 0; } int main (int, char * []) { try { Stock *s = new Stock ("Alice's Restaurant", 20, 7, 11, 13); Bond *b = new Bond ("City Infrastructure", 10, 2, 3, 5); Portfolio::instance()->add (s); Portfolio::instance()->add (b); Portfolio::instance()->print (); Portfolio::fini(); } catch (Portfolio::error_condition &e) { cout << "Portfolio error: " << e << endl; return -1; } catch (...) { cout << "unknown error" << endl; return -2; } return 0; }
An Indexed Variation of the Singleton Pattern class Portfolio { public: static Portfolio * instance(Agent *); static void fini(Agent *); ... private: static map<Agent *, Portfolio *> instances_; Portfolio (); virtual ~Portfolio (); ... }; map<Agent *, Portfolio *> Portfolio::instances_; Portfolio * Portfolio::instance(Agent *a) { Portfolio * p = 0; map<Agent *, Portfolio *>::iterator i = instances_.find(a); if (i == instances_.end()) { p = new Portfolio; instances_.insert(make_pair(a,p)); } else { p = i->second; } return p; } void Portfolio::fini(Agent *a) { map<Agent*,Portfolio*>:: iterator i = instances_.find(a); if (i != instances_.end()) { Portfolio * p = i->second; instances_.erase(i); delete p; } } void Agent::buy (Security *s) { int cost = s->shares_ * s->current_value_; if (cost > reserve_) { throw cannot_afford; } Portfolio::instance(this)-> add(s); reserve_ -= cost; } Agent::~Agent () { Portfolio::fini(this); }
Polymorphic (Deep) Copying • Challenges • C++ does not have a virtual copy constructor • However, may need to duplicate polymorphic collections • All the securities in a portfolio (which are actually stocks or bonds) • All the animals in a zoo (which are actually Giraffes or Ostriches) • Copy construction depends on the concrete types involved • Motivates use of the Prototype pattern • Wraps copy construction within a common virtual method • Each subclass overrides that method: uses its specific copy constructor with dynamic allocation to “clone” itself
Prototype Pattern • Problem • Need to duplicate objects with different dynamic types • Context • Virtual constructors are not available (e.g., in C++) • However, polymorphic method invocations are supported • Solution core • Provide a polymorphic method that returns an instance of the same type as the object on which the method is called • Polymorphic method calls copy constructor, returns base class pointer or reference to concrete derived type • Consequences • Emulates virtual copy construction behavior • Allows anonymous duplication of heterogeneous types
Use of the Prototype Pattern struct Security { public: … virtual Security * clone () = 0; ... }; Security * Stock::clone () { return new Stock(*this); } Security * Bond::clone () { return new Bond(*this); } Security * Agent::sell (Security *s) { Security * current = Portfolio::instance(this)->find(s); if (current ==0) { throw cannot_provide; } Security * copy = current->clone(); Portfolio::instance(this)->remove(current); reserve_ += copy->shares_ * copy->current_value_; return copy; }
Interacting with Heterogeneous Collections • Challenges • Polymorphism lets you aggregate different types together • However, how to interact depends on the specific types • Motivates use of the Visitor pattern • Lets the program discover how to interact with each concrete type through an initial “handshake” with it • Dispatches that interaction directly through a method call
Visitor Pattern • Problem • We have a heterogeneous collection of objects over which we need to perform type-specific operations • Context • Run-time type identification (if available) adds overhead • Want to avoid unnecessary interactions among types • Types in collection change less frequently than the set of operations that are to be performed over them • Solution core • Modify types in the collection to support double dispatch • If you cannot, RTTI / dynamic casting can be used similarly (a variant?) • Consequences • Once modified in this way, any of the types can handshake with arbitrary “visitors” to give correct behavior
Basic Use of the Visitor Pattern struct ProjectedValueFunctor : public SecurityVisitor { int & value_; ProjectedValueFunctor (int & value); virtual ~ProjectedValueFunctor (); void operator () (Security * s) { s->accept(this); } virtual void visit_stock (Stock * s) { if (s) {value_ += s->shares_ * (s->projected_value_ + s->dividend_);} } virtual void visit_bond (Bond * b) { if (b) {value_ += b->shares_ * (b->projected_value_ + b->interest_);} } }; int Portfolio::projected_value () { int value = 0; for_each (securities_.begin(), securities_.end(), ProjectedValueFunctor(value)); return value; } struct SecurityVisitor { virtual ~SecurityVisitor(); virtual void visit_stock (Stock *) = 0; virtual void visit_bond (Bond *) = 0; }; struct Security { … virtual void accept (SecurityVisitor * sv) = 0; }; void Stock::accept (SecurityVisitor * sv) { if (sv) {sv->visit_stock(this);} } void Bond::accept (SecurityVisitor * sv) { if (sv) {sv->visit_bond(this);} }
Design Patterns Summary • We’ve looked at a number of patterns this semester • Iterator: access elements sequentially no matter how stored • Factory method: create a related type polymorphically • Adapter: converts an interface you have into one you want • Memento: packages up object state without violating encapsulation • Observer: tell registered observers when state changes • Singleton: provides access to a single instance (possibly per index) • Prototype: allows polymorphic duplication of heterogeneous types • Visitor: allows interaction with heterogeneous collections • Think about how patterns can drive design • From basic abstractions towards a working program with refinements • Lab 5 will involve the Singleton and Memento patterns • CSE 432 focuses on combining patterns of this sort (design) • CSE 532 focuses on other kinds of patterns (architectural)