C++ 学习 12 设计模式 2

    xiaoxiao2021-03-31  35

    工厂方法抽象工厂原型模式构建器单例模式享元模式门面模式代理模式适配器中介者

    对象创建模式

    通过对象创建模式绕开new,来避免对象创建(new),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。 典型模式 Factory Method Abstract Factory Prototype Builder

    工厂模式

    动机:在软件系统中经常面临着创建对象的工作;由于需求的变化,需要创建的对象具体类型经常变化。如何应对这种变化?如何绕开常规的对象创建方法(new),提供一种封装机制来避免客户程序和这种具体对象创建工作的紧耦合?

    定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。FactoryMethod 使得一个雷德实例化延迟(目的:解耦,手段:虚函数)到子类。 例 如下文件分割器需要支持多种类型文件分割

    class ISplitter{ public: virtual void split()=0; virtual ~ISplitter(){} }; class BinarySplitter : public ISplitter{ }; class TxtSplitter: public ISplitter{ }; class PictureSplitter: public ISplitter{ }; class VideoSplitter: public ISplitter{ }; class MainForm : public Form { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar; public: void Button1_Click(){ ISplitter * splitter= new BinarySplitter();//依赖具体类 splitter->split(); } };

    违反了依赖倒置原则,稳定不能依赖变化,高层不能依赖底层。 应用工厂模式

    //抽象类 class ISplitter{ public: virtual void split()=0; virtual ~ISplitter(){} }; //工厂基类 class SplitterFactory{ public: virtual ISplitter* CreateSplitter()=0; virtual ~SplitterFactory(){} }; //具体类 class BinarySplitter : public ISplitter{ }; class TxtSplitter: public ISplitter{ }; class PictureSplitter: public ISplitter{ }; class VideoSplitter: public ISplitter{ }; /具体工厂 class BinarySplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new BinarySplitter(); } }; class TxtSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new TxtSplitter(); } }; class PictureSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new PictureSplitter(); } }; class VideoSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new VideoSplitter(); } }; class MainForm : public Form { SplitterFactory* factory;//工厂 public: MainForm(SplitterFactory* factory){ this->factory=factory; } void Button1_Click(){ ISplitter * splitter= factory->CreateSplitter(); //多态new splitter->split(); } };

    对每一子类均有一个工厂类来实现其new这样在高层代码中就可以利用多态性来避免其依赖具体底层类。根据客户端传入的具体类类型来实现晚绑定。 总结 Factory Method 模式用于隔离类对象的使用者和其具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。 Factory Method 模式通过面向对象的手法, 将所要创建的具体对象工作延迟到子类,从而实现一种扩展的策略,较好地解决了这种紧耦合关系/ Factory Method 模式解决单个对象的需求变化。缺点在于要求穿件方法/参数相同。

    抽象工厂

    动机: 在软件系统中,经常面临一系列互相依赖的对象的创建工作;同时由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法new提供一种封装机制来避免客户程序和这种多系列具体对象创建工作的紧耦合? 定义:提供一个接口,让该接口负责穿件一系列的相关或者相互依赖的对象,无须制定它们具体的类。 例一个数据库操作类需要有connection,command, reader等几个相互依赖的类。

    class IDBConnection{ }; class IDBCommand{ }; class IDataReader{ }; class SqlConnection: public IDBConnection{ }; class SqlCommand: public IDBCommand{ }; class SqlDataReader: public IDataReader{ }; class EmployeeDAO{ public: vector<EmployeeDO> GetEmployees(){ SqlConnection* connection = new SqlConnection(); connection->ConnectionString = "..."; SqlCommand* command = new SqlCommand(); command->CommandText="..."; command->SetConnection(connection); SqlDataReader* reader = command->ExecuteReader(); while (reader->Read()){ } } };

    采用工厂方法:

    //数据库访问有关的基类 class IDBConnection{ }; class IDBConnectionFactory{ public: virtual IDBConnection* CreateDBConnection()=0; }; class IDBCommand{ }; class IDBCommandFactory{ public: virtual IDBCommand* CreateDBCommand()=0; }; class IDataReader{ }; class IDataReaderFactory{ public: virtual IDataReader* CreateDataReader()=0; }; //支持SQL Server class SqlConnection: public IDBConnection{ }; class SqlConnectionFactory:public IDBConnectionFactory{ }; class SqlCommand: public IDBCommand{ }; class SqlCommandFactory:public IDBCommandFactory{ }; class SqlDataReader: public IDataReader{ }; class SqlDataReaderFactory:public IDataReaderFactory{ }; //支持Oracle class OracleConnection: public IDBConnection{ }; class OracleCommand: public IDBCommand{ }; class OracleDataReader: public IDataReader{ }; class EmployeeDAO{ IDBConnectionFactory* dbConnectionFactory; IDBCommandFactory* dbCommandFactory; IDataReaderFactory* dataReaderFactory; public: vector<EmployeeDO> GetEmployees(){ IDBConnection* connection = dbConnectionFactory->CreateDBConnection(); connection->ConnectionString("..."); IDBCommand* command = dbCommandFactory->CreateDBCommand(); command->CommandText("..."); command->SetConnection(connection); //关联性 IDBDataReader* reader = command->ExecuteReader(); //关联性 while (reader->Read()){ } } };

    由于connection ,command, reader具有关联系不能保证外部传入的具体实例正确性。 采用抽象工厂

    //数据库访问有关的基类 class IDBConnection{ }; class IDBCommand{ }; class IDataReader{ }; class IDBFactory{ public: virtual IDBConnection* CreateDBConnection()=0; virtual IDBCommand* CreateDBCommand()=0; virtual IDataReader* CreateDataReader()=0; }; //支持SQL Server class SqlConnection: public IDBConnection{ }; class SqlCommand: public IDBCommand{ }; class SqlDataReader: public IDataReader{ }; class SqlDBFactory:public IDBFactory{ public: virtual IDBConnection* CreateDBConnection(); virtual IDBCommand* CreateDBCommand(); virtual IDataReader* CreateDataReader(); }; //支持Oracle class OracleConnection: public IDBConnection{ }; class OracleCommand: public IDBCommand{ }; class OracleDataReader: public IDataReader{ }; class EmployeeDAO{ IDBFactory* dbFactory; public: vector<EmployeeDO> GetEmployees(){ IDBConnection* connection = dbFactory->CreateDBConnection(); connection->ConnectionString("..."); IDBCommand* command = dbFactory->CreateDBCommand(); command->CommandText("..."); command->SetConnection(connection); //关联性 IDBDataReader* reader = command->ExecuteReader(); //关联性 while (reader->Read()){ } } };

    总结, 如果没有对应多系列对象构建的需求变化,则没有必要使用Abstract Factory 模式,这时候使用简单的工厂完全可以。 系列对ixang指的是在某一特定系列下的对象之间有相互依赖或相互作用的关系,不同系列的对象之间不能相互依赖。 Abstract Factory 模式主要在于应对新系列的需求变动,其缺点在于难以应对新对象的需求变动。

    原型模式

    动机:在软件系统中,经常面临着某些结构复杂的对象的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但他们却拥有比较稳定一致的接口。 如何应对这种变化?如何向客户程序(使用这些对象的程序)隔离出这些易变对象从而使得依赖这些易变对象的客户程序不随着需求改变而改变? 定义:使用原型实例制定创建对象的种类,然后通过拷贝这些原型来创建新对象。

    //prototype //抽象类 class ISplitter{ public: virtual void split()=0; virtual ISplitter* clone()=0; //通过克隆自己来创建对象 virtual ~ISplitter(){} }; //具体类 class BinarySplitter : public ISplitter{ public: virtual ISplitter* clone(){ return new BinarySplitter(*this); } }; class TxtSplitter: public ISplitter{ public: virtual ISplitter* clone(){ return new TxtSplitter(*this); } }; class PictureSplitter: public ISplitter{ public: virtual ISplitter* clone(){ return new PictureSplitter(*this); } }; class VideoSplitter: public ISplitter{ public: virtual ISplitter* clone(){ return new VideoSplitter(*this); } }; //Client class MainForm : public Form { ISplitter* prototype;//原型对象 public: MainForm(ISplitter* prototype){ this->prototype=prototype; } void Button1_Click(){ ISplitter * splitter= prototype->clone(); //克隆原型 splitter->split(); } };

    此模式需要对原对象拷贝构造函数有良好的实现。 总结 Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些易变类拥有稳定的接口。 Prototype 模式对于如何创建易变类的实例对象采用原型克隆的方法来做,它使得我们可以非常灵活地动态创建拥有某些稳定接口的新对象–所需工作仅仅是注册一个新类的对象,然后在任何需要的地方Clone。Prototype模式中的Clone 方法可以利用某些框架中的序列化来实现深拷贝。

    Builder模式

    动机:在软件系统中,有时候面临着一个复杂对象的创建工作,其通常是由各个部分的字对象用一定的算法构成的;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定。如何应对这种变化?如何提供一种封装机制来隔离出复杂对象的各个部分的变化,从而保持系统中的稳定构建算法不随着需求改变而改变? 定义:将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定) 可以创建不同的表示(变化)。

    class House{ //.... }; class HouseBuilder { public: House* GetResult(){ return pHouse; } virtual ~HouseBuilder(){} protected: House* pHouse; virtual void BuildPart1()=0; virtual void BuildPart2()=0; virtual void BuildPart3()=0; virtual void BuildPart4()=0; virtual void BuildPart5()=0; }; class StoneHouse: public House{ }; class StoneHouseBuilder: public HouseBuilder{ protected: virtual void BuildPart1(){ //pHouse->Part1 = ...; } virtual void BuildPart2(){ } virtual void BuildPart3(){ } virtual void BuildPart4(){ } virtual void BuildPart5(){ } }; class HouseDirector{ public: HouseBuilder* pHouseBuilder; HouseDirector(HouseBuilder* pHouseBuilder){ this->pHouseBuilder=pHouseBuilder; } House* Construct(){ pHouseBuilder->BuildPart1(); for (int i = 0; i < 4; i++){ pHouseBuilder->BuildPart2(); } bool flag=pHouseBuilder->BuildPart3(); if(flag){ pHouseBuilder->BuildPart4(); } pHouseBuilder->BuildPart5(); return pHouseBuilder->GetResult(); } };

    对比template method , 模板方法只是实现流程的晚绑定, 而构建器模式则是提供一个抽象类将流程隔离(houseDirector)不同的builder实现不同的流程。此处housedirector 为稳定部分。

    总结:Builder模式主要用于 分布构建一个复杂的对象。而在这其中分步骤是一个稳定的算法而复杂对象的各个部分经常变化。变化点在哪里就封装那里–Builder模式主要在于应对复杂对象的各个部分的需求变动。其缺点在于难以应对分步骤构建算法的需求变动。 在builder模式中要注意不同语言中构造器内调用虚函数的差别。

    接口隔离模式

    在组建构建过程中某些结构之间的直接依赖常常会带来很多问题,甚至无法实现。采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案。 典型模式 Facade 门面模式,Proxy,Adapter,Mediator.

    门面模式

    动机:客户和组件中的各种复杂子系统有了过多的耦合随着外部刻画程序的变化哈各子系统的演化,这种过多的耦合会面临很多的挑战。 如何简化外部刻画程序和系统间的交互接口如何将外部客户程序的演化和内部子系统之间的相互依赖解耦? 定义: 为子系统中的一组接口提供一个一致(稳定)界面Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。 总结 从客户程序角度来看Facade模式简化了整个组件系统的接口,对于组件内部与外部客户程序来说达到了一种解耦的效果,内部子系统的任何变化不会影响到Facade接口的变化。 Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次,Facade很多时候更是一种架构设计模式。 Facade设计模式并非一个集装箱,可以任意地放进任何多个对象。Facade模式中组件的内部应该是互相耦合关系比较大的一系列组件,而不是一个简单的功能集合。

    Proxy代理模式

    动机:在面向对象系统中,有些对象由于某些原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要某些进程外的访问等),直接访问会给使用者,或者系统结构带来很多麻烦。 如何在不识趣透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中的常见解决方式。 定义:为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。

    class ISubject{ public: virtual void process(); }; class RealSubject: public ISubject{ public: virtual void process(){ //.... } }; class ClientApp{ ISubject* subject; public: ClientApp(){ subject=new RealSubject(); } void DoTask(){ //... subject->process(); //.... } };

    假设访问realsubject需要增加间接性,采用Proxy模式如下:

    class ISubject{ public: virtual void process(); }; //Proxy的设计 class SubjectProxy: public ISubject{ public: virtual void process(){ //对RealSubject的一种间接访问 //.... } }; class ClientApp{ ISubject* subject; public: ClientApp(){ subject=new SubjectProxy(); } void DoTask(){ //... subject->process(); //.... } };

    总结: 增加一层间接是软件系统中对许多复杂问题的一种常见解决方案。在面向对象系统中直接使用某些对象会带来很多问题,作为间接层的Proxy对象是解决这一问题的常用手段。 具体Proxy设计模式的实现方法,实现粒度都相差很大,有些可能是对象粒度的控制,如copy-on-write技术,有些可能是对组建模块提供抽象代理曾,在架构层次对对象做Proxy。 Proxy并不要求保持接口的完整一致性,只要能实现间接控制,有时候损及一些透明性是可以接受的。

    Adapter 适配器模式

    动机 :在软件系统中,由于应用环境的变化,常常需要将一些现存的对象放在新的应用环境中,按时新环境要求的接口是这些现存的对象不满足的。如何应对这种迁移的变化?如何既能利用现有对象的良好实现,同时又能够满足新应用环境的接口? 定义:将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能易其工作的那些类可以一起工作。

    //目标接口(新接口) class ITarget{ public: virtual void process()=0; }; //遗留接口(老接口) class IAdaptee{ public: virtual void foo(int data)=0; virtual int bar()=0; }; //遗留类型 class OldClass: public IAdaptee{ //.... }; //对象适配器 class Adapter: public ITarget{ //继承 protected: IAdaptee* pAdaptee;//组合 public: Adapter(IAdaptee* pAdaptee){ this->pAdaptee=pAdaptee; } virtual void process(){ int data=pAdaptee->bar(); pAdaptee->foo(data); } }; //类适配器 class Adapter: public ITarget, protected OldClass{ //多继承 } int main(){ IAdaptee* pAdaptee=new OldClass(); ITarget* pTarget=new Adapter(pAdaptee); pTarget->process(); } class stack{ deqeue container; }; class queue{ deqeue container; };

    适配器模式分为两种一种对象适配,另一种类适配(上述多继承) 总结:Adapter 模式主要应用于希望服用一些现存的雷,但是接口又与复用环境不兼容的情况,在一流代码复用,类库迁移等方面非常有用。 GOF23定义了两种 Adapter模式的实现结构:对象适配器和 类适配器。但类适配器采用多继承的实现方式,一般不推荐使用。对象适配器采用组合的方式更符合松耦合的精神。 Adapter模式可以实现的非常灵活,不必拘泥于GOF23中定义的两种结构。例如 STL 中 stack 和queue使用deque来实现。set/map 使用rb_tree来实现。

    Mediator中介者

    动机:在软件构建中,经常会出现多个对象互相关联交互的情况,对象之间经常会维持一种复杂的引用关系,如果遇到需求更改,这种直接的引用关系将面临不断的变化。在这种情况下我们可以使用一个中介对象来管理对象间的关联关系,来避免互相交互的对象之间的紧耦合引用关系,从而更好地抵御变化。 定义:使用一个中介来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖-〉运行时依赖),从而使其耦合松散 (管理变化),而且可以独立地改变它们之间的交互。 总结:将多个对象间的复杂关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变多个对象互相管理为多个对象和一个中介者关联,简化了系统的维护,抵御了可能的变化。 岁uzhekongzhiluoj的复杂化,Mediator具体对象的实现肯呢个相当复杂,这时候可以对Mediator对象进行分解处理。 Facade是解耦系统间(单向)的对象联系;Mediator是解耦系统内各个对象之间的(双向)的关联关系。

    对象性能模式

    面向对象很好的解决路抽象的文题,但是必不可避免的要付出一定的代价,对于通常来讲,面向对象的成本大都可以忽略不计。但是某些情况下,面向对象所带来的成本需谨慎处理。 典型模式: Singleton Flyweight

    Singleton单例模式

    动机:在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能保证他们的逻辑正确性,以及良好的效率。 如何绕过常规构造器,提供一种机制来保证一个类只有一个实例?这应该是类设计者的责任,而不是使用者的责任。 定义: 保证一个类仅有一个实例,并提供一个该实例的全局访问点。 线程非安全版本

    class Singleton{ private: Singleton(); Singleton(const Singleton& other); public: static Singleton* getInstance(); static Singleton* m_instance; }; Singleton* Singleton::m_instance=nullptr; //线程非安全版本 Singleton* Singleton::getInstance() { if (m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }

    线程安全版

    Singleton* Singleton::getInstance() { Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }

    错误版本

    //双检查锁,但由于内存读写reorder不安全 Singleton* Singleton::getInstance() { if(m_instance==nullptr){ Lock lock; if (m_instance == nullptr) { m_instance = new Singleton(); } } return m_instance; }

    new三步骤有可能在编译期间 reorder故容易出错。 C++11 新方法

    //C++ 11版本之后的跨平台实现 (volatile) std::atomic<Singleton*> Singleton::m_instance; std::mutex Singleton::m_mutex; Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release);//释放内存fence m_instance.store(tmp, std::memory_order_relaxed); } } return tmp; }

    总结:Singleton模式中的实例构造器可以设置为protected以允许子类派生。 Singleton 模式一般不要支持拷贝构造函数和clone接口,因为这有可能导致多个实例对象,与Singleton初衷违背。

    Flyweight享元模式

    动机:在软件系统采用纯粹对象反感的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价–主要是指内存需求方面的代价。如何能够避免大量细粒度对象问题的同时,让外部客户程序仍能够透明地使用面向最想的方式来操作? 定义:运用共享技术有效地支持大量细粒度的对象。

    class Font { private: //unique object key string key; //object state //.... public: Font(const string& key){ //... } }; ß class FontFactory{ private: map<string,Font* > fontPool; public: Font* GetFont(const string& key){ map<string,Font*>::iterator item=fontPool.find(key); if(item!=footPool.end()){ return fontPool[key]; } else{ Font* font = new Font(key); fontPool[key]= font; return font; } } void clear(){ //... } };

    使用map结构来构造fontpool防止font对象过多。 总结:flyweight 主要解决面向对象的代价问题,一般不触及面向对象的抽象问题。Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力,在具体实现方面要注意对象状态的处理。

    转载请注明原文地址: https://ju.6miu.com/read-665451.html

    最新回复(0)