• Design Patterns Example Code (in C++)


    Overview

    Design patterns are ways to reuse design solutions that other software developers have created for common and recurring problems. The design patterns on this page are from the book Design Patterns, Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison Wesley, 1995, ISBN 0-201-63361-2. More information on this book can be found at: http://www.awl.com/cseng/ and the source code can be found at: http://hillside.net/patterns/DPBook/Source.html. This book is commonly referred to as the "Gang of Four" book (abbreviated as GoF), or "Gamma et al".

    Most of the design patterns in the GoF book use inheritance and run-time polymorphism (virtual function) as implementation solutions, although a few have templatized solutions. Expect to see more and more template based design patterns or variations on these design patterns for templates.

    Design patterns are not function or class "building block solutions". In other words, a design pattern does not provide a reusable source code component. Instead, the design approach to solve a particular problem in a given context is reused, and the actual code to implement the design pattern is different for each particular problem. The design pattern provides a framework that is customized for each particular problem need.

    The benefits from these patterns include reductions in design and debugging time (sometimes quite dramatic), with the drawbacks being sometimes a slight hit in performance or an increase in the total amount of classes needed for an application. In almost every case where a design pattern is used, the benefits far exceed the drawbacks.

    Singleton Design Pattern

    The Singleton design pattern ensures only one instance of a class in an application (more generally it provides a framework to control the instantiations of a particular class, so that more than one instantiation could be provided, but under the control of the Singleton class). The GoF book discusses Singleton patterns, while Meyers discusses general techniques for limiting object instantiation in item 26 of More Effective C++. In Singleton classes, all of the regular constructors are publicly disabled (put in the private section), and access to the singleton object is through a static method which creates a controlled instance of the class and returns a reference to this controlled instance. An example that Meyers uses is:

    class Printer {
    public:
      static Printer& thePrinter();
      // ...
    private:
      Printer(); // no public creation of Printer objects
      Printer (const Printer& rhs);
      // ...
    };
    Printer& Printer::thePrinter() {
      static Printer p;
      return p;
    }
    // example usage:
      Printer::thePrinter().reset();
      Printer::thePrinter().submitJob(buffer);

    Note that this example implementation code will not work if the Singleton is accessed in a multi-threaded environment, since there may be two (or more) threads trying to simultaneously access the Singleton for the first time, causing a conflict in the static instance creation. Some form of mutex protection must be provided in this scenario.

    There are many flavors of Singletone and quite a few subtle complexities, although the general principle of the pattern makes it one of the easiest to understand. One potential complexity is controlled destruction of the internal Singleton instance (i.e. when is it destructed and in what order compared to other Singletons or global objects).


    Proxy

    Proxy classes act as a stand-in for other another type, particularly a low-level type that is not built-in to the language. They allow easier creation, manipulation (particularly assignment into), and serialization of the object, and in some cases allow operations that would not be possible or would be inefficient if performed on the more low-level data. Meyers (More Effective C++, item 30) uses the examples of 2D and 3D proxies, as well as a ProxyChar type. In these examples, lvalue versus rvalue distinctions can be made (write versus read access), and different logic performed depending on which is needed.

    In the GoF book, more examples of Proxy usages include:

    • Remote proxy - provides a local representative for an object in a different address space.
    • Virtual proxy - creates expensive objects on demand (e.g. large image file, loaded when first accessed).
    • Protection proxy - controls access to the original object.
    • Smart reference - counted pointers, reference counting, smart pointers.

    Note that the typical meaning of a Proxy is as a higher level abstraction for an existing lower-level entity or datatype. An example is the URL assignment UrlProxy abstract base class, declared as the following:

    class UrlProxy {
    public:
      UrlProxy (const std::string& server, const std::string& originalUrl);
      virtual ~UrlProxy() = 0; // important to have virtual dtor
    
      bool sameServer (const UrlProxy&) const;
    
      virtual UrlProxy* clone() const = 0; // implement Prototype design pattern
    
      virtual void openClientApp() const = 0; // start up appropriate client
    
    protected:
       const std::string& getOriginalUrl() const { return mOriginalUrl; }
    
    private:
       std::string mServer;
       std::string mOriginalUrl;
    };

    This base class provides an higher-leve abstraction of a URL which would typically be stored as a string in an application. It allows simpler comparisons of the URL host name (server), which is a case-insensitive compare. It can provide a framework for selecting between URL types (e.g. Http, Ftp, MailTo), and simplifying the parsing and manipulation of URL paths, IP ports, and e-mail addresses. Without a Proxy class, each application would have to duplicate this code, or know how to call the appropriate functions in the right sequence.


    Factory Method

    The Factory Method design pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate (this is also know as a virtual constructor). This design pattern is a good example of overall increased complexity and slightly reduced performance, with the benefit of greatly increased flexibility, extendibility, and design robustness. This pattern is used in more complex creational design patterns such as Abstract Factory and Builder.

    The Factory Method works by creating a parallel inheritance hierarchy to the primary inheritance hierarchy. The parallel set of classes are responsible for polymorphically creating an object of the primary set of classes. The parallel set of classes are typically called Factory classes, since they produce objects of the primary classes.

    An example is the URL assignment UrlFactory and UrlFactoryMgr class declarations. The UrlFactory pointers have been wrapped in a smart pointer class, but raw pointers could be used instead (UrlFactory*):

    #include <boost/smart_ptr.hpp>
    
    // The class hierarchy that the Factory uses is rooted in UrlProxy
    class UrlFactory {
    public:
      UrlFactory();
      virtual ~UrlFactory() = 0;
      // copy ctor and assign op are implicit
    
      // return null pointer from following fct if can't create object
      virtual UrlProxy* createInstance(const std::string& str) = 0;
    };
    
    class UrlFactoryMgr { 
    public:
      static UrlFactoryMgr& theUrlFactoryMgr();
      void registerFactory (boost::shared_ptr<UrlFactory>);
      // return null pointer from following fct if can't create object
      UrlProxy* createInstance (const std::string& str);
      ~UrlFactoryMgr();
    private:
      UrlFactoryMgr() : mFactories() { }
      UrlFactoryMgr (const UrlFactoryMgr&); // copy ctor
      UrlFactoryMgr& operator= (const UrlFactoryMgr&); // assign op
    private:
      std::list<boost::shared_ptr<UrlFactory> > mFactories; // note space in '> >'
    };

    Each UrlFactory derived class knows exactly how to create an object of the corresponsing UrlProxy class. It gets the data for creating the UrlProxy object from the std::string passed in to the createInstance method.

    Higher level code first creates a UrlFactory object for each derived UrlProxy type it cares about, then registers that with the UrlFactoryMgr (which is a Singleton) using the registerFactory method. Then UrlProxy objects are created by taking each candidate string (which may or may not be a URL string, and if it is a URL string it can one of many derived types) and passing it to the UrlFactoryMgrcreateInstance method. This method asks each UrlFactory object if it can create an instance (a null pointer return means no), and when it finds the first UrlFactory that says yes, that value is returned. If all of them say no, then that result is also returned.


    Prototype (Clone)

    A typical need in many classes and applications is the ability to clone an object virtually (this is also called the Prototype design pattern). The clone method (Meyers talks about this in item 25 of More Effective C++) provides a way to create a copy (clone) of an object through a virtual function (constructors, including copy ctors, are not allowed to be virtual). Another way of summarizing this is that many times a collection (or selected entries) of derived objects needs to be copied / cloned for usage (possibly for another collection or to be manipulated in some fashion). Without virtual functions, a large if or switch statement is needed, along with the associated maintenance problems. A clone method can be defined that simply new's a copy of itself using the copy ctor. This takes advantage of the recent relaxation of virtual signature matching rules in the C++ standard (called co-variant return type). An example from an event processing framework:

    class Event {
    public:
      virtual time_t getTimeStamp() const = 0;
      virtual const char* representation() const = 0;
      virtual Event* clone() const = 0;
    };
    class CoinReleaseEvent : public Event {
    public:
      CoinReleaseEvent* clone() const { return new CoinReleaseEvent(*this); }
      // ...
    };

    The generalized framework code can then make a copy of an Event object at any time without needing to know the actual derived type.


    State

    The State design pattern is a very useful way to encapsulate and simplify state processing (particularly if the state transitions and actions need to be enhanced or changed in some fashion). For example, a Vehicle class might have the following states and actions:

    • OFF -> (turn ignition on) -> IDLE
    • IDLE -> (engage gear) -> MOVING
    • MOVING -> (set gear to park) -> IDLE
    • IDLE -> (turn ignition off) -> OFF

    The pattern could be implemented with the following classes:

      class Vehicle {
      public:
        Vehicle ();
        // whatever other ctors are needed
        void turnOn(); // { mState->turnOn(*this); }
        void engageGear(); // { mState->engageGear (*this); }
        void disengageGear(); //{ mState->disengageGear (*this); }
        void turnOff(); //{ mState->turnOff (*this); }
        // other operations
      private:
        friend class VehState;
        void changeState (VehState* newState); // { mState = newState; }
      private:
        VehState* mState;
      };
    
      class VehState {
      public:
        virtual void turnOn(Vehicle&); // allows changing Vehicle object state pointer
        virtual void engageGear(Vehicle&); // same as above
    
        virtual void disengageGear(Vehicle&);
        virtual void turnOff(Vehicle&);
      protected:
        void changeState (Vehicle& veh, VehState* newState) { veh.changeState(newState); }
      };
    
      class MovingState : public VehState {
      public:
        static MovingState& theMovingState(); // Singleton design pattern
        virtual void disengageGear(Vehicle& veh);
      };
    
      class IdleState : public VehState {
      public:
        static IdleState& theIdleState(); // Singleton design pattern
        virtual void engageGear(Vehicle& veh) {changeState(veh, &MovingState::theMovingState()); }
      };
    
      class OffState : public VehState {
      public:
        static OffState& theOffState(); // Singleton design pattern
        virtual void turnOn(Vehicle& veh) { changeState(veh, &IdleState::theIdleState()); }
      };
      
      // implement default behavior in VehState method implementations
      
      // implementations of Vehicle methods:
      
      Vehicle::Vehicle () :
        mState(&OffState::theOffState()) { }
      void Vehicle::turnOn() {
        mState->turnOn(*this);
      }
      void Vehicle::engageGear() {
        mState->engageGear (*this);
      }
      void Vehicle::disengageGear() {
        mState->disengageGear (*this);
      }
      void Vehicle::turnOff() {
        mState->turnOff (*this);
      }
      void Vehicle::changeState (VehState* newState) {
        mState = newState;
      }

    Note that the protected changeState method in VehState is needed because friendship is not inherited in derived classes. The changeState method effectively 'forwards' the request from each derived state class.


    Observer

    The Observer design pattern is also known as Publish-Subscribe (which is similar to but different in some ways from Publish-Subscribe messaging). It defines a one-to-many relationship between objects so that when one object changes states, all of the dependents are notified and can update themselves accordingly. Example code from GoF (the C++ code has been enhanced and improved):

      #include <list>
    
      class Subject;
      class Observer {
      public:
        virtual ~Observer();
        // Observer (const Observer& ); // implicit
        // Observer& operator= (const Observer& ); // implicit
    
        virtual bool update(const Subject& theChangedSubject) = 0;
    
      protected:
        Observer(); // protected default ctor
      };
    
      class Subject {
      public:
        virtual ~Subject();
        // Subject (const Subject& ); // implicit
        // Subject& operator= (const Subject& ); // implicit
    
        virtual void attach(Observer*);
        virtual void detach(Observer*);
        virtual bool notify(); // bool return for a failure condition
      protected:
        Subject(); // protected default ctor
      private:
        typedef std::list<Observer*> ObsList;
        typedef ObsList::iterator ObsListIter;
        ObsList mObservers;
      };
    
      void Subject::attach (Observer* obs) {
        mObservers.push_back(obs);
      }
    
      void Subject::detach (Observer* obs) {
        mObservers.remove(obs);
      }
    
      bool Subject::notify () {
        ObsList detachList;
        for (ObsListIter i (mObservers.begin()); i != mObservers.end(); ++i) {
          if (!(*i)->update(*this)) {
            detachList.push_back(*i);
          }
        }
        for (ObsListIter j (detachList.begin()); j != detachList.end(); ++j) {
          detach(*j);
        }
        return true; // always return true, but may be different logic in future
      }
    
      class ClockTimer : public Subject {
      public:
        ClockTimer();
        virtual int getHour() const;
        virtual int getMinute() const;
        virtual int getSecond() const;
        void tick();
      };
    
      void ClockTimer::tick () {
        // update internal time-keeping state
        // ...
        notify();
      }
    
      class Widget { /* ... */ };
      // Widget is a GUI class, with virtual function named draw
    
      class DigitalClock: public Widget, public Observer {
      public:
        explicit DigitalClock(ClockTimer&);
        virtual ~DigitalClock();
        virtual void update(const Subject&); // overrides Observer virtual update fct
        virtual void draw(); // overrides Widget virtual draw fct
      private:
        ClockTimer& mSubject;
      };
    
      DigitalClock::DigitalClock (ClockTimer& subject) : mSubject(subject) {
        mSubject.attach(this);
      }
    
      DigitalClock::~DigitalClock () {
        mSubject.detach(this);
      }
    
      void DigitalClock::update (const Subject& theChangedSubject) {
        if (&theChangedSubject == &mSubject) {
          draw();
        }
      }
    
      void DigitalClock::draw () {
        // get the new values from the subject
        int hour (mSubject.getHour());
        int minute (mSubject.getMinute());
        // ... and draw the digital clock
      }
    
      class AnalogClock : public Widget, public Observer {
      public:
        AnalogClock(ClockTimer&);
        virtual void update(const Subject&);
        virtual void draw();
        // ...
      };
    
      // application code
      ClockTimer timer;
      AnalogClock analogClock(timer);
      DigitalClock digitalClock(timer);
      // ...

    Strategy

    The Strategy pattern allows different algorithms to be determined with a class rather than client code having to select which algorithm to use (or having to select between types / classes that differ only in the internal implementation of an algorithm). The pattern defines and encapsulates a family of algorithms and lets them be used interchangeably. Strategy presents a common interface for the needed functionality to the client code, with one algorithm or implementation selected at a time.

    Strategy can be implemented through inheritance, with an ABC defining the interface, or through a template class or function. A template approach works well if the algorithm to be used is known at compile time, otherwise a more dynamic approach through virtual functions is usually used.

    A Context class is used by the application / client code, while a Strategy class hierarchy (or template class or function) provides the interface for the algorithm. The algorithm to be used can be specified through the Context interface (giving the client code the flexibility of choosing an algorithm) or could be selected from within the Context class.

    Some example code:

    #include <vector>
    
    // two algorithms, one checks for a point contained within
    // a convex polygon, the other within a concave polygon
    template <typename P> // P is point type
    bool ptInConvexPoly (const P& pt, const std::vector<P>& v);
    
    template <typename P> // P is point type
    bool ptInConcavePoly (const P& pt, const std::vector<P>& v);
    
    // Approach 1, using inheritance / virtual functions
    
    template <typename P>
    class PtInPoly {
    public:
      // ...
      virtual bool ptInPoly(const P& pt,
                            const std::vector<P>& v) const = 0;
    };
    
    template <typename P>
    class ConvexPtInPoly : public PtInPoly<P> {
    public:
      // ... assume Singleton for this example
      virtual bool ptInPoly(const P& pt,
                            const std::vector<P>& v) const {
        return ptInConvexPoly (pt, v);
      }
    };
    template <typename P>
    class ConcavePtInPoly : public PtInPoly<P> {
    public:
      // ... assume Singleton for this example
      virtual bool ptInPoly(const P& pt,
                            const std::vector<P>& v) const {
        return ptInConcavePoly (pt, v);
      }
    };
    
    template <typename P> // P is point type
    class Polygon {
    public:
      // ... various ctors and methods, initialize
      // mPtInPoly by calling checkConvex method
      bool ptInPoly (const P& pt) const {
        return mPtInPoly->ptInPoly (pt, mPts);
      }
      void addPoint (const P& pt) {
        if (checkConvex(mPts)) {
          mPtInPoly = &ConvexPtInPoly<P>::theConvexPtInPoly();
        }
        else {
          mPtInPoly = &ConcavePtInPoly<P>::theConcavePtInPoly();
        }
      }
    private:
      std::vector<P> mPts;
      const PtInPoly<P>* mPtInPoly;
    };
    
    // Approach 2, templatized Strategy, compile-time selection
    // somewhat similar to STL functors
    
    template <typename P, typename F>
    class Polygon {
    public:
      // ... various ctors and methods
      bool ptInPoly (const P& pt) const {
        return mF.ptInPoly(pt, mPts);
      }
    private:
      std::vector<P> mPts;
      F mF;
    };
    
    template <typename P>
    class ConvexPtInPoly {
    public:
      // possibly other stuff here
      bool ptInPoly(const P& pt, const std::vector<P>& v) const {
        return ptInConvexPoly (pt, v);
      }
    };
    template <typename P>
    class ConcavePtInPoly {
    public:
      // possibly other stuff here
      bool ptInPoly(const P& pt, const std::vector<P>& v) const {
        return ptInConcavePoly (pt, v);
      }
    };
    
    // example usage:
      Polygon<TwoD, ConcavePtInPoly> poly;
      // ...
      if (poly.ptInPoly(p)) {
      // ...
      
  • 相关阅读:
    第五次站立会议
    第四次站立会议
    第三次晚间站立总结会议
    易校小程序典型用户需求分析
    第三次站立会议
    第二次晚间站立总结会议
    第二次站立会议
    第一次晚间站立总结会议
    MyBatis注解
    延迟加载与缓存
  • 原文地址:https://www.cnblogs.com/UnGeek/p/4486012.html
Copyright © 2020-2023  润新知