I. Designing Use Case Realizations with Gang of Four Design Patterns (Ch. 23) A. adapter (23.1) ------------------------------------------ ADAPTER (23.1) Problem: How to resolve incompatible interfaces? How to provide stable interface to similar components with different interfaces? Solution: An intermediate object that translates calls from one interface to the other. Example: ------------------------------------------ ------------------------------------------ C++ HEADER FILES // $RCSfile: AccountingAdapter.h,v $ #ifndef AccountingAdapter_h #define AccountingAdapter_h #include "CreditPayment.h" #include "Reservation.h" class AccountingAdapter { public: virtual void postReceivable(CreditPayment *p); virtual void postSale(Reservation *r); }; #endif // $RCSfile: SAPAccountingAdapter.h,v $ #ifndef SAPAccountingAdapter_h #define SAPAccountingAdapter_h #include "AccountingAdapter.h" #include "SAPAccounting.h" class SAPAccountingAdapter : public AccountingAdapter { public: virtual void postReceivable(CreditPayment *p); virtual void postSale(Reservation *r); private: SAPAccounting *accounts; }; #endif ------------------------------------------ ------------------------------------------ // C++ CODE FILES // $RCSfile: AccountingAdapter.cpp,v $ #include "AccountingAdapter.h" // $RCSfile: SAPAccountingAdapter.cpp,v $ #include "SAPAccountingAdapter.h" void SAPAccountingAdapter:: postReceivable(CreditPayment *p) { accounts->receive(p->pennies(), p->description()); } void SAPAccountingAdapter:: postSale(Reservation *r) { accounts->sell(r->pennies(), r->description()); } ------------------------------------------ 1. Discussion 2. related patterns 3. analysis discoveries during design (23.2) B. Factory (23.3) 1. separation of concerns 2. factories ------------------------------------------ FACTORY (23.3) Problem: who should create objects when: - the exact class will vary - creation logic is complex Solution: assign responsibility to a factory class Example: ------------------------------------------ ------------------------------------------ // $RCSfile: ServicesFactory.h,v $ #ifndef ServicesFactory_h #define ServicesFactory_h #include "AccountingAdapter.h" class ServicesFactory { private: AccountingAdapter * accountingAdapter; // ... public: virtual AccountingAdapter * getAccountingAdapter(); // ... }; #endif ------------------------------------------ ------------------------------------------ // $RCSfile: ServicesFactory.cpp,v $ #include "ServicesFactory.h" #include #include "SAPAccountingAdapter.h" #include "TopAccountingAdapter.h" AccountingAdapter * ServicesFactory::getAccountingAdapter() { const char *sysname = getenv("ACCOUNTINGSYSTEM"); if (strcmp(sysname,"SAP") == 0) { return new SAPAccountingAdapter(); } else if (strcmp(sysname,"Top") == 0) { return new TopAccountingAdapter(); } else { // ... return 0; } } ------------------------------------------ 3. discussion 4. related patterns 5. also known as C. Singleton (23.4) would be a good idea to pass the ServicesFactory class around the program? how many instances of the factory are needed? ------------------------------------------ SINGLETON (23.4) Problem: How to ensure that a class has only one instance? How to provide global access to a single instance object? How to allow the instance to be extended by subtyping, without affecting clients? Solution: Use a static method (class operation) to return the instance. Hide the class's constructor. Example: ------------------------------------------ ------------------------------------------ // $RCSfile: ServicesFactory.h,v $ #ifndef ServicesFactory_h #define ServicesFactory_h #include "AccountingAdapter.h" class ServicesFactory { private: AccountingAdapter * accountingAdapter; // ... public: virtual AccountingAdapter * getAccountingAdapter(); // ... static ServicesFactory * Instance(); protected: // hide the default constructor ServicesFactory(); // hide the copy constructor ServicesFactory(ServicesFactory &sf); private: static ServicesFactory * _instance; }; #endif ------------------------------------------ ------------------------------------------ // $RCSfile: ServicesFactory.cpp,v $ #include "ServicesFactory.h" #include #include "SAPAccountingAdapter.h" #include "TopAccountingAdapter.h" AccountingAdapter * ServicesFactory::getAccountingAdapter() { const char *sysname = getenv("ACCOUNTINGSYSTEM"); if (strcmp(sysname,"SAP") == 0) { return new SAPAccountingAdapter(); } else if (strcmp(sysname,"Top") == 0) { return new TopAccountingAdapter(); } else { // ... return 0; } } ServicesFactory * ServicesFactory::Instance() { if (_instance == 0) { _instance = new ServicesFactory(); } return _instance; } // need to initialize _instance so the // Instance method has somewhere to start ServicesFactory * ServicesFactory::_instance = 0; ------------------------------------------ 1. UML shorthand interaction diagrams 2. implementation and design issues a. lazy vs. eager initialization b. static methods instead of the singleton pattern why not add a static method getAccountingAdapter to ServicesFactory? 3. related patterns D. summary of access to external services with varying interfaces (23.5) E. Strategy (23.6) ------------------------------------------ STRATEGY (23.6) Problem: How to allow related algorithms to vary independently from clients that use them? How to support "pluggable" code? How to hide data needed by algorithms from clients. How to avoid lots of conditional logic to select algorithms. Solution: Define each algorithm in a separate class, with a common interface. Document the common specification in the interface, but use underspecification, to allow for the variation. Example: ------------------------------------------ ------------------------------------------ CONTEXT OBJECT HEADER // $RCSfile: Reservation.h,v $ #ifndef Reservation_h #define Reservation_h #include "Money.h" class PricingStrategy; class Reservation { public: // ... virtual Money *getTotal(); virtual Money *getPreDiscountTotal(); private: PricingStrategy *pricingStrategy; }; #endif ------------------------------------------ ------------------------------------------ CONTEXT OBJECT IMPLEMENTATION // $RCSfile: Reservation.cpp,v $ #include "Reservation.h" #include "PricingStrategy.h" long Reservation::pennies() { return getTotal()->pennies(); } const char* Reservation::description() { return "a reservation"; } Money * Reservation::getTotal() { return pricingStrategy->getTotal(this); } Money *Reservation::getPreDiscountTotal(){ vector::iterator i; Money *tot = new Money(); i = flights->begin(); for (; i != 0; i++) { tot = *tot + *((*i)->getTotal()); } return tot; } ------------------------------------------ ------------------------------------------ STRATEGY INTERFACE HEADER // $RCSfile: PricingStrategy.h,v $ #ifndef PricingStrategy_h #define PricingStrategy_h #include "Money.h" #include "Reservation.h" class PricingStrategy { public: virtual Money * getTotal(Reservation *context) = 0; virtual ~PricingStrategy() = 0; protected: Reservation *reservation; }; #endif ------------------------------------------ ------------------------------------------ STRATEGY INTERFACE IMPLEMENATION // $RCSfile: PricingStrategy.cpp,v $ #include "PricingStrategy.h" ------------------------------------------ 1. discussion Where is the pricing strategy work really being done? a. creation How should the pricing strategy objects be created? b. data for strategies 2. related patterns What patterns are related? F. Composite and other design principles (23.7) ------------------------------------------ COMPOSITE (23.7) Problem: how to treat a group of objects in the same way as a single objects are treated? Solution: Define an interface that both the composite and atomic objects implement. Record the common specification in this interface. Example: ------------------------------------------ ------------------------------------------ // $RCSfile: CompositePricingStrategy.h,v$ #ifndef CompositePricingStrategy_h #define CompositePricingStrategy_h #include "PricingStrategy.h" #include class CompositePricingStrategy : public PricingStrategy { public: virtual Money * getTotal(Reservation *context) = 0; virtual void add(PricingStrategy *ps); protected: vector * pricingStrategies; }; #endif ------------------------------------------ ------------------------------------------ //$RCSfile:CompositePricingStrategy.cpp,v$ #include "CompositePricingStrategy.h" void CompositePricingStrategy ::add(PricingStrategy *ps) { pricingStrategies->insert( pricingStrategies->begin(), ps); } ------------------------------------------ ------------------------------------------ // BestForCustomerPricingStrategy.h #ifndef BestForCustomerPricingStrategy_h #define BestForCustomerPricingStrategy_h #include "CompositePricingStrategy.h" class BestForCustomerPricingStrategy : public CompositePricingStrategy { public: virtual Money * getTotal(Reservation *context); }; #endif ------------------------------------------ ------------------------------------------ // BestForCustomerPricingStrategy.cpp #include \ "BestForCustomerPricingStrategy.h" Money * BestForCustomerPricingStrategy ::getTotal(Reservation *context) { vector::iterator i; i = pricingStrategies->begin(); Money *lowest = (*i)->getTotal(context); i++; for (; i != 0; i++) { Money *price = (*i)->getTotal(context); if (*price < *lowest) { lowest = price; } } return lowest; } ------------------------------------------ 1. discussion a. inheritance and abstract classes What is the difference between an abstract class and an interface? in the implementation of this last pricing strategy, where does the field (data member) pricingStrategies come from? How do pricing strategies get added to the composite? b. passing aggregate objects as parameters Why pass Reservation objects to the pricing strategy, instead of just passing the collection of flights? c. creation of multiple strategies at what point in making a reservation can we discover new pricing strategies that would apply to the reservation? What would a use case for getting the senior citizen discounts pricing strategies created Work through that design (see figures 23.17-18) d. other uses of the composite pattern creating macros of commands, which are just composites of commands e. exercises Q: how would you handle getting 25 percent off the cost of a reservation for taking a business class flight on Tuesday? How would you handle a buy one get one free sale? G. Facade (23.8) why can't we just use the strategy pattern? ------------------------------------------ FACADE (23.8) Problem: How to minimize coupling between subsystems? How to provide a higher-level interface that makes a subsystem easier to use? How to provide a simple default view of a subsystem? Solution: Define a single object, the facade, that has responsibility for interacting with the rest of the system. Example: ------------------------------------------ is a handler a kind of facade object? ------------------------------------------ // $RCSfile: Compiler.h,v $ #ifndef Compiler_h #define Compiler_h #include class Compiler { public: Compiler(); virtual void Compile (istream *); }; #endif ------------------------------------------ ------------------------------------------ // $RCSfile: Compiler.cpp,v $ #include "Compiler.h" void Compiler::Compile(istream * s) { /* ... do all the work... */ } ------------------------------------------ 1. discussion how many copies of the facade object do we need? do you really have to hide th other classes behind the facade? what's the impact of this pattern on coupling? in a design that uses patterns, we tend to have lots of small classes. How does this affect clients? 2. related patterns what's the difference between adapter and this pattern? H. Observer or Publish-Subscribe or Delegation Event Model (23.9) Why not just have the classes in the domain logic layer directly call the classes in the user interface layer? ------------------------------------------ OBSERVER OR PUBLISH-SUBSCRIBE (23.9) problem: How to allow events (state changes) to affect other objects, without coupling to them? How to support a one-to-many dependency between objects? How to separate layers that must talk to each other? Roles: A *publisher* or *subject* notifies other objects of an event. A *subscriber* or *observer* wishes to be notified of events. ------------------------------------------ In our Reservation/UI example, which is the publisher? Which is the subscriber? ------------------------------------------ SOLUTION Solution: Define an "Observer" (or "Listener") interface, which documents the kinds of events and the information they carry. Subscribers/Observers implement the listener interface. Publishers have methods to dynamically register subscribers. Publishers notify all registered subscribers when an event occurs. ------------------------------------------ Can there be more than one subscriber? Zero? ------------------------------------------ OBSERVER INTERFACE // $RCSfile: ReservationObserver.h,v $ #ifndef ReservationObserver_h #define ReservationObserver_h class Reservation; class ReservationObserver { public: virtual void update(Reservation *r) = 0; virtual ~ReservationObserver(); protected: ReservationObserver(); }; #endif ------------------------------------------ ------------------------------------------ OBSERVER IMPLEMENTATION // $RCSfile: ReservationObserver.cpp,v $ #include "ReservationObserver.h" ------------------------------------------ ------------------------------------------ // $RCSfile: Reservation.h,v $ #ifndef Reservation_h #define Reservation_h #include "Money.h" #include "Flight.h" #include "ReservationObserver.h" #include class PricingStrategy; class Reservation { public: // ... Reservation(PricingStrategy *ps); virtual ~Reservation(); virtual void subscribe( ReservationObserver *ro); virtual void publish(); private: PricingStrategy *pricingStrategy; vector *flights; vector *subscribers; }; #endif ------------------------------------------ ------------------------------------------ // $RCSfile: Reservation.cpp,v $ #include "Reservation.h" #include "PricingStrategy.h" // ... void Reservation::enterFlight(Flight *f) { flights->insert(flights->end(), f); publish(); } Reservation::Reservation( PricingStrategy *ps) : pricingStrategy(ps), flights(new vector()), subscribers( new vector()) { } Reservation::~Reservation() { delete pricingStrategy; delete flights; // can't delete the observers... } void Reservation::subscribe( ReservationObserver *ro) { subscribers->insert( subscribers->end(), ro); } void Reservation::publish() { vector::iterator i; i = subscribers->begin(); for (; i != 0; i++) { (*i)->update(this); } } ------------------------------------------ 1. Java version 2. discussion a. many subscribers are possible b. unsubscribing In some applications in the subscribers will want to unsubscribe themselves dynamically. How would you support that? c. event terminology d. not just for connecting user interfaces and models 3. related patterns Why do want to protect against variation in the user interface?