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)) {
  // ...
  
时间: 2024-10-14 05:19:41

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

Learning JavaScript Design Patterns The Observer Pattern

The Observer Pattern The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state. When a subject needs to notify observers about s

Learning JavaScript Design Patterns The Module Pattern

The Module Pattern Modules Modules are an integral piece of any robust application's architecture and typically help in keeping the units of code for a project both cleanly separated and organized. In JavaScript, there are several options for impleme

Learning JavaScript Design Patterns -- A book by Addy Osmani

Learning JavaScript Design Patterns A book by Addy Osmani Volume 1.6.2 Tweet Copyright © Addy Osmani 2015. Learning JavaScript Design Patterns is released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license. It

How I explained Design Patterns to my wife: Part 1

Introduction Me and my wife had some interesting conversations on Object Oriented Design principles. After publishing the conversation on CodeProject, I got some good responses from the community and that really inspired me. So, I am happy to share o

Design Principles from Design Patterns

Leading-Edge Java A Conversation with Erich Gamma, Part III by Bill Venners June 6, 2005 Erich Gamma lept onto the software world stage in 1995 as co-author of the best-selling book Design Patterns: Elements of Reusable Object-Oriented Software (Addi

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

Design Patterns Simplified - Part 3 (Simple Factory)[设计模式简述--第三部分(简单工厂)] This article explains why and how to use the Simple Factory Design Pattern in software development. 这篇文章解释了在软件开发中为什么使用,以及怎么使用简单工厂模式. I am here to continue the discussion of Desi

Design patterns in Spring Framework

This article is the 4th about design patterns used in Spring framework. It'll present new 3 patterns implemented in this framework. At the begin, we'll discover 2 patterns belonging to the family of structural patterns: adapter and decorator. At the

Design Patterns Uncovered: The Chain Of Responsibility Pattern

Chain of Responsibility in the Real World The idea of the Chain Of Responsibility is that it avoids coupling the sender of the request to the receiver, giving more than one object the opportunity to handle the request.  This process of delegation app

Notes on Design Patterns

一些笔记. strategy : facilitates the switch of the different but related algorithms/behaviors observer proxy : controls the access to the real subject ; shares the common interface with the real subject and sometimes provides a subset of the operations i