本文同时发在: http://cpper.info/2016/01/16/Five-Create-Patterns-Of-Oriented-Object.html。html
本文主要讲述设计模式中的五种建立型设计模式。linux
建立型模式主要关注对象的建立过程,将对象的建立过程进行封装,使客户端能够直接获得对象,而不用去关心如何建立对象。
这里共有5种建立型模式:编程
保证一个类仅有一个实例,并提供一个访问它的全局访问点。设计模式
Singleton 模式解决问题十分常见: 咱们怎样去建立一个惟一的变量( 对象)? 好比能够经过全局变量来解决,可是一个全局变量使得一个对象能够被访问,但它不能防止你实例化多个对象。网络
一个更好的办法是,让类自身负责保存它的惟一实例。这个类能够保证没有其余实例能够被建立(经过截取建立新对象的请求),而且它能够提供一个访问该实例的方法。这就是Singleton模式。数据结构
template <class T> class Singleton { public: static T* getInstancePtr() { if(0 == proxy_.instance_) { createInstance(); } return proxy_.instance_; } static T& getInstanceRef() { if(0 == proxy_.instance_) { createInstance(); } return *(proxy_.instance_); } static T* createInstance() { return proxy_.createInstance(); } static void deleteInstance() { proxy_.deleteInstance(); } private: struct Proxy { Proxy() : instance_(0) { } ~Proxy() { if(instance_) { delete instance_; instance_ = 0; } } T* createInstance() { T *p = instance_; if(p == 0) { zl::thread::LockGuard<zl::thread::Mutex> guard(lock_); if((p = instance_) ==0) { instance_ = p = new T; } } return instance_; } void deleteInstance() { if(proxy_.instance_) { delete proxy_.instance_; proxy_.instance_ = 0; } } T *instance_; zl::thread::Mutex lock_; }; protected: Singleton() { } ~Singleton() { } private: static Proxy proxy_; }; // usage class SomeMustBeOneObject : private Singleton<SomeMustBeOneObject> {} SomeMustBeOneObject* o1 = Singleton<SomeMustBeOneObject>::getInstancePtr(); SomeMustBeOneObject* o2 = Singleton<SomeMustBeOneObject>::getInstancePtr(); SomeMustBeOneObject& o3 = Singleton<SomeMustBeOneObject>::getInstanceRef(); assert(o1 == o2); assert(o1 == &o3); SomeMustBeOneObject* o4 = new SomeMustBeOneObject; // Compile Error! SomeMustBeOneObject o5; // Compile Error!
Singleton 算是最简单的一个模式了,可是最初想写对一个好用的Singleton也是有不少困难的。好比下面的这几个实现(都有问题):框架
template <class T> class Singleton1 { public: static T* getInstancePtr() { if(instance_ == NULL) { instance_ = new T; } return T; } private: T* instance_; }; template <class T> class Singleton2 { public: static T* getInstancePtr() { LockGuard(); if(instance_ == NULL) { instance_ = new T; } return T; } private: T* instance_; }; template <class T> class Singleton3 { public: static T* getInstancePtr() { if(instance_ == NULL) { LockGuard(); instance_ = new T; } return T; } private: T* instance_; }; template <class T> class Singleton4 { public: static T* getInstancePtr() { if(instance_ == NULL) { LockGuard(); if(instance_ == NULL) { instance_ = new T; } } return T; } private: T* instance_; };
Singleton 模式是设计模式中最为简单、最为常见、最容易实现,也是最应该熟悉和掌握的模式。虽然简单,但曾经也是有很多坑的,上面Singleton一、Singleton二、Singleton3这几个实现其实都是错的,具体错在哪仍是比较容易发现的,而Singleton4看似不错,但其实也不是彻底正确的(本文最初给出的Singleton
单例模式最好的实现应该使用linux下的pthread_once或者使用C++11的std_once或者C++编译器进行编译。好比:模块化
class SomeClass // 必定要使用C++11编译器编译 { public: SomeClass* getInstancePtr() { static SomeClass one; return &one; } private: SomeClass(); SomeClass(const SomeClass&); SomeClass& operator=(const SomeClass&); }
Singleton 模式常常和 Factory( Abstract Factory) 模式在一块儿使用, 通常来讲系统中工厂对象通常来讲只须要一个。函数
通常来讲有几种状况须要用到Factory Method:
#define TYPE_PROXY_1 1 #define TYPE_PROXY_2 2 class Proxy { public: virtual Proxy() {} int type() const { return type_; } private: int type_; }; class Proxy1 : public Proxy { public: virtual Proxy1() {} private: // some attributes }; class Proxy2 : public Proxy { public: virtual Proxy2() {} }; class ProxyFactory { public: typedef std::function<Proxy*()> CreateCallBack; typedef std::function<void(Proxy*)> DeleteCallBack; public: static void registerProxy(int type, const CreateCallBack& ccb, const DeleteCallBack& dcb) { proxyCCB_[type] = ccb; proxyDCB_[type] = dcb; } static Proxy* createProxy(int type) { auto iter = proxyCCB_.find(type); if(iter!=proxyCCB_.end()) return iter->second(); return defaultCreator(type); } static void deleteProxy(Proxy* proxy) { assert(proxy); auto iter = proxyDCB_.find(proxy->type()); if(iter!=proxyDCB_.end()) iter->second(proxy); else delete proxy; } private: static Proxy* defaultCreator(int type); static std::map<int, CreateCallBack> proxyCCB_; static std::map<int, DeleteCallBack> proxyDCB_; }; /*static*/ Proxy* ProxyFactory::defaultCreator(int type) { switch (type) { case TYPE_PROXY_1: return new Proxy1(type); case TYPE_PROXY_1: return new Proxy2(type); } return NULL; } // usage class Proxy3 : public Proxy { public: virtual Proxy3() {} static Proxy* create() { return new Proxy3; } static void destory(Proxy* p) { delete p; } }; ProxyFactory factory; factory.registerProxy(1000, &Proxy3::create, &Proxy3::destory); Proxy* p1 = factory.createProxy(TYPE_PROXY_1); Proxy* p2 = factory.createProxy(TYPE_PROXY_2); Proxy* p3 = factory.createProxy(1000);
Factory 模式在实际开发中应用很是普遍,面向对象的系统常常面临着对象建立问题:要么是要建立的类实在是太多了, 要么是开始并不知道要实例化哪个类。Factory 提供的建立对象的接口封装,能够说部分地解决了实际问题。
固然Factory 模式也带来一些问题, 好比没新增一个具体的 ConcreteProduct 类,均可能要修改Factory的接口,这样 Factory 的接口永远就不能封闭(Close)。 这时咱们能够经过建立一个 Factory 的子类来经过多态实现这一点,或者经过对新的ConcreteProduct向Factory注册一个建立回调的函数。
用于建立一组相关或相互依赖的复杂对象。
好比网络游戏中须要过关打怪,对于不一样等级的玩家,应该生成与此相应的怪物和场景,好比不一样的场景,动物,等等。
又好比一个支持多种视感标准的用户界面工具包,例如 Motif 和 Presentation Manager。不一样的视感风格为诸如滚动条、窗口和按钮等用户界面“窗口组件”定义不一样的外观和行为。为保证视感风格标准间的可移植性,一个应用不该该为一个特定的视感外观硬编码它的窗口组件。在整个应用中实例化特定视感风格的窗口组件类将使得之后很难改变视感风格。
class AbstractProductA { public: virtual ~AbstractProductA(); }; class AbstractProductB { public: virtual ~AbstractProductB(); }; class ProductA1: public AbstractProductA { }; class ProductA2: public AbstractProductA { }; class ProductB1: public AbstractProductB { }; class ProductB2: public AbstractProductB { }; class AbstractFactory { public: virtual ~AbstractFactory(); virtual AbstractProductA *CreateProductA() = 0; virtual AbstractProductB *CreateProductB() = 0; }; class ConcreteFactory1: public AbstractFactory { public: AbstractProductA *CreateProductA() { return new ProductA1; } AbstractProductB *CreateProductB() { return new ProductB1; } }; class ConcreteFactory2: public AbstractFactory { public: AbstractProductA *CreateProductA() { return new ProductA2; } AbstractProductB *CreateProductB() { return new ProductB2; } }; //usage int main(int argc, char *argv[]) { AbstractFactory *cf1 = new ConcreteFactory1(); cf1->CreateProductA(); cf1->CreateProductB(); AbstractFactory *cf2 = new ConcreteFactory2(); cf2->CreateProductA(); cf2->CreateProductB(); }
在如下状况可使用 Abstract Factory模式
Abstract Factory 模式和 Factory 模式二者比较类似,可是仍是有区别的,AbstractFactory 模式是为建立一组(有多种不一样类型)相关或依赖的对象提供建立接口, 而 Factory 模式是为一类对象提供建立接口或延迟对象的建立到子类中实现。而且Abstract Factory 模式一般都是使用 Factory 模式实现的。
将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。
对于一个大型的、复杂对象,咱们但愿将该对象的建立过程与其自己的表示或数据结构相分离。
当咱们要建立的对象很复杂的时候(一般是由不少其余的对象组合而成),咱们要将复杂对象的建立过程和这个对象的表示(展现)分离开来, 这样作的好处就是经过一步步的进行复杂对象的构建, 因为在每一步的构造过程当中能够引入参数,使得通过相同的步骤建立最后获得的对象的展现不同。
class Builder { public: virtual ~Builder() {} virtual void BuildPartA(const string &buildPara) = 0; virtual void BuildPartB(const string &buildPara) = 0; virtual void BuildPartC(const string &buildPara) = 0; virtual Product *GetProduct() = 0; }; class ConcreteBuilder: public Builder { public: void BuildPartA(const string &buildPara) { cout << "Step1:Build PartA..." << buildPara << endl; } void BuildPartB(const string &buildPara) { cout << "Step1:Build PartB..." << buildPara << endl; } void BuildPartC(const string &buildPara) { cout << "Step1:Build PartC..." << buildPara << endl; } Product *GetProduct() { BuildPartA("pre-defined"); BuildPartB("pre-defined"); BuildPartC("pre-defined"); return new Product(); } }; class Director { public: Director(Builder *bld) { _bld = bld ; } void Construct() { _bld->BuildPartA("user-defined"); _bld->BuildPartB("user-defined"); _bld->BuildPartC("user-defined"); } private: Builder *_bld; }; // usage Director *d = new Director(new ConcreteBuilder()); d->Construct();
另一个例子,好比Java语言中的StringBuilder类(听说用来构造字符串对象时很是高效,相比String类):
StringBuilder MyStringBuilder = new StringBuilder("Your total is "); MyStringBuilder.AppendFormat("{0:C} ", MyInt); Console.WriteLine(MyStringBuilder); MyStringBuilder.Insert(6,"Beautiful "); MyStringBuilder.Remove(5,7);
Builder 模式经过一步步建立对象,并经过相同的建立过程能够得到不一样的结果对象(每一步的建立过程均可以绑定不一样的参数)
用原型实例指定建立对象的种类,而且经过拷贝这些原型建立新的对象, 也即经过一个已存在对象来进行新对象的建立。
你能够经过定制一个通用的图形编辑器框架和增长一些表示音符、休止符和五线谱的新对象来构造一个曲谱编辑器。这个编辑器框架可能有一个工具选择板用于将这些音乐对象加到曲谱中。这个选择板可能还包括选择、移动和其余操纵音乐对象的工具。用户能够点击四分音符工具并使用它将四分音符加到曲谱中。或者他们可使用移动工具在五线谱上上下移动一个音符,从而改变它的音调。
咱们假定该框架为音符和五线谱这样的图形构件提供了一个抽象的Graphics类。此外,为定义选择板中的那些工具,还提供一个抽象类Tool。该框架还为一些建立图形对象实例并将它们加入到文档中的工具预约义了一个GraphicsTool子类。但GraphicsTool给框架设计者带来一个问题。音符和五线谱的类特定于咱们的应用,而GraphicsTool类却属于框架。GraphicsTool不知道如何建立咱们的音乐类的实例,并将它们添加到曲谱中。咱们能够为每一种音乐对象建立一个GraphicsTool的子类,但这样会产生大量的子
类,这些子类仅仅在它们所初始化的音乐对象的类别上有所不一样。咱们知道对象复合是比建立子类更灵活的一种选择。问题是,该框架怎么样用它来参数化GraphicsTool的实例,而这些实例是由Graphics类所支持建立的。
解决办法是让GraphicsTool经过拷贝或者“克隆”一个Graphics子类的实例来建立新的Graphics,咱们称这个实例为一个原型。GraphicsTool将它应该克隆和添加到文档中的原型做为参数。若是全部Graphics子类都支持一个Clone操做,那么GraphicsTool能够克隆全部种类的Graphics。
所以在咱们的音乐编辑器中,用于建立个音乐对象的每一种工具都是一个用不一样原型进行初始化的GraphicsTool实例。经过克隆一个音乐对象的原型并将这个克隆添加到曲谱中,每一个GraphicsTool实例都会产生一个音乐对象。
class Prototype { public: virtual Prototype *Clone() const = 0; }; class ConcretePrototype: public Prototype { public: Prototype *Clone() const { return new ConcretePrototype(*this); } }; //use Prototype *p = new ConcretePrototype(); Prototype *p1 = p->Clone(); Prototype *p2 = p->Clone();
Prototype 模式经过复制原型(Prototype)而得到新对象建立的功能,这里 Prototype 自己就是“对象工厂”(由于可以生产对象)。
并且Prototype和Factory仍是很类似的, Factory是由外部类负责产品的建立,而Prototype是由类自身负责产品的建立。
首先,在编程中,对象的建立一般是一件比较复杂的事,由于,为了达到下降耦合的目的,咱们一般采用面向抽象编程的方式,对象间的关系不会硬编码到类中,而是等到调用的时候再进行组装,这样虽然下降了对象间的耦合,提升了对象复用的可能,但在必定程度上将组装类的任务都交给了最终调用的客户端程序,大大增长了客户端程序的复杂度。采用建立类模式的优势之一就是将组装对象的过程封装到一个单独的类中,这样,既不会增长对象间的耦合,又能够最大限度的减少客户端的负担。
其次,使用普通的方式建立对象,通常都是返回一个具体的对象,即所谓的面向实现编程,这与设计模式原则是相违背的。采用建立类模式则能够实现面向抽象编程。客户端要求的只是一个抽象的类型,具体返回什么样的对象,由建立者来决定。
再次,能够对建立对象的过程进行优化,客户端关注的只是获得对象,对对象的建立过程则不关心,所以,建立者能够对建立的过程进行优化,例如在特定条件下,若是使用单例模式或者是使用原型模式,均可以优化系统的性能。
全部的建立类模式本质上都是对对象的建立过程进行封装。
建立型模式的目标都是相同的,即负责产品对象的建立。其中Singleton是使某一产品只有一个实例,Factory Method负责某一产品(及其子类)的建立,Abstract Factory负责某一系列相关或相互依赖产品(及相应子类)的建立,Builder模式经过一些复杂协议或者复杂步骤建立某一产品,Prototype则是经过复制自身来建立新对象。
一般来讲Abstract Factory能够经过Factory来实现,且通常都是Singleton模式。
着重推荐Singleton、Factory、Abstract Factory模式,而Builder和Prototype目前我直接使用的还不多。
接下来来模拟一个虚拟场景来演示这5种建立型模式的组合使用。假设有一个关于车辆的组装系统,目前有汽车(Car)和货车(Truck),每种车由发动机和车轮子构成(忽略其余零件),且可能有不一样的发动机和车轮子,好比汽车的发动机和轮子与货车的就不同,甚至不一样类型的汽车的发动机和轮子也可能不彻底同样。我将在该场景中同时使用这五种模式,以研究各模式的使用场景以及相互间的配合。
请等待下文介绍, 连接在此。