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)) {
  // ...
  

Design Patterns Example Code (in C++)的更多相关文章

  1. Design Patterns Simplified - Part 3 (Simple Factory)【设计模式简述--第三部分(简单工厂)】

    原文链接:http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part3-factory/ Design ...

  2. Head First Design Patterns

    From Head First Design Patterns. Design Principle: Idnetify the aspects of your application that var ...

  3. Apex Design Patterns

    Apex allows you to build just about any custom solution on the Force.com platform. But what are the ...

  4. Learning JavaScript Design Patterns The Observer Pattern

    The Observer Pattern The Observer is a design pattern where an object (known as a subject) maintains ...

  5. Learning JavaScript Design Patterns The Module Pattern

    The Module Pattern Modules Modules are an integral piece of any robust application's architecture an ...

  6. Design Patterns笔记

    一些笔记. strategy : facilitates the switch of the different but related algorithms/behaviors observer p ...

  7. BookNote: Refactoring - Improving the Design of Existing Code

    BookNote: Refactoring - Improving the Design of Existing Code From "Refactoring - Improving the ...

  8. How I explained Design Patterns to my wife: Part 1

    Introduction Me and my wife had some interesting conversations on Object Oriented Design principles. ...

  9. Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications

    January 2014 Containing twenty-four design patterns and ten related guidance topics, this guide arti ...

随机推荐

  1. C++的历史与现状

    在31年前(1979年),一名刚获得博士学位的研究员,为了开发一个软件项目发明了一门新编程语言,该研究员名为Bjarne Stroustrup,该门语言则命名为——C with classes,四年后 ...

  2. 纯css3实现的鼠标悬停动画按钮

    今天给大家带来一款纯css3实现的鼠标悬停动画按钮.这款按钮鼠标经过前以正方形的形式,当鼠标经过的时候以动画的形式变成圆形.效果图如下: 在线预览   源码下载 实现的代码. html代码: < ...

  3. java自带线程池和队列详细讲解<转>

    Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后 ...

  4. PHP多进程(4) :内部多进程

    说的都是只兼容unix 服务器的多进程,下面来讲讲在window 和 unix 都兼容的多进程(这里是泛指,下面的curl实际上是通过IO复用实现的). 通过扩展实现多线程的典型例子是CURL,CUR ...

  5. dp - HNU 13404 The Imp

    The Imp Problem's Link: http://acm.hnu.cn/online/?action=problem&type=show&id=13404&cour ...

  6. selenium运行火狐报错FirefoxDriver : Unable to connect to host 127.0.0.1 on port 7055

    摘要: 这是个常见的启动firefoxdriver的问题,具体的错误日志如下,其实原因很简单,就是你的Selenium版本和firefox 不兼容了. Firefox 版本太高了, 请及时查看你安装的 ...

  7. ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点

    LinkedList接口(在代码的使用过程中和ArrayList没有什么区别) ArrayList底层是object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点. 而在L ...

  8. 多个return和一个return

    //一个returnnamespace CleanCSharp.Methods.Dirty { class MethodExitPoints { public string GenerateAgeAp ...

  9. Spring.NET学习笔记——目录(原)

    目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...

  10. 说说M451例程讲解之定时器

    关于定时器 相信很多人都不会陌生,无论是51还是32,任何微控制器,都会有定时器 定时器控制器包含 4 组 32-位定时器,TIMER0~TIMER3,提供用户便捷的计数定时功能.定时器可执行很多功能 ...