创建型模式(CreationalPattern)是对类的实例化过程的抽象化,能够提供对象的创建和管理职责。创建型模式共有5种:
工厂方法模式(FactoryMethodPattern)又叫虚拟构造函数(VirtualConstructor)模式或者多态性工厂(PolymorphicFactory)模式。工厂方法模式的用意是定义一个创建产品对象的工厂接口,将实际创建性工作推迟到子类中,即定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
当产品种类过多,由于每一种产品都需要实现一个工厂类,增加了代码量
抽象工厂模式是工厂方法模式的升级版本。在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
使用建造者模式的典型场景如下。
■相同的方法,不同的执行顺序,产生不同的结果时,可以采用建造者模式。
■多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
■产品类非常复杂,或者产品类中的方法调用顺序不同产生了不同的效能,这个时候使用建造者模式。
■在对象创建过程中会使用到系统的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段没有发现,要通过创建者模式柔化创建过程,本身已经违反设计的最初目标。
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
■性能优良:原型模式是在内存二进制流的复制,要比直接new一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
■逃避构造函数的约束:这既是优点也是缺点,直接在内存中复制,构造函数是不会执行的,因此减少了约束,需要在实际应用时进行权衡考虑。
■资源优化场景,类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
■性能和安全要求的场景,通过new产生一个对象需要非常烦琐的数据准备或访问权限,可以使用原型模式。
■一个对象多个修改者的场景,一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供调用者使用。
1#include
确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
单例模式的主要作用是确保一个类只有一个实例存在。单例模式可以用在建立目录、数据库连接等需要单线程操作的场合,用于实现对系统资源的控制。
第一次引用类时,才进行对象实例化。
1classSingleton2{3public:4staticSingleton*getInstance();5~Singleton(){}67private:8Singleton(){}//构造函数私有,防止通过拷贝构造,赋值运算实例化对象9Singleton(constSingleton&obj)=delete;//明确拒绝10Singleton&operator=(constSingleton&obj)=delete;//明确拒绝1112staticSingleton*m_pSingleton;13};1415Singleton*Singleton::m_pSingleton=NULL;1617Singleton*Singleton::getInstance()18{19if(m_pSingleton==NULL)20{21m_pSingleton=newSingleton;22}23returnm_pSingleton;24}
1std::mutexmt;23classSingleton4{5public:6staticSingleton*getInstance();7private:8Singleton(){}//构造函数私有9Singleton(constSingleton&)=delete;//明确拒绝10Singleton&operator=(constSingleton&)=delete;//明确拒绝1112staticSingleton*m_pSingleton;1314};15Singleton*Singleton::m_pSingleton=NULL;1617Singleton*Singleton::getInstance()18{19if(m_pSingleton==NULL)20{21mt.lock();22if(m_pSingleton==NULL)23{24m_pSingleton=newSingleton();25}26mt.unlock();27}28returnm_pSingleton;29}
类加载时,就进行对象实例化
1//饿汉式:线程安全,注意一定要在合适的地方去delete它2classSingleton3{4public:5staticSingleton*getInstance();6private:7Singleton(){}//构造函数私有8Singleton(constSingleton&)=delete;//明确拒绝9Singleton&operator=(constSingleton&)=delete;//明确拒绝1011staticSingleton*m_pSingleton;12};1314Singleton*Singleton::m_pSingleton=newSingleton();1516Singleton*Singleton::getInstance()17{18returnm_pSingleton;19}
结构型模式(StructuralPattern)描述如何将类或者对象结合在一起形成更大的结构。结构型模式的目的是通过组合类或对象产生更大结构以适应更高层次的逻辑需求,
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
■适配器模式可以让两个没有任何关系的类在一起运行。
■增加了类的透明性。
■提高类的复用度。
■增强代码的灵活性。
适配器模式的使用场景使用适配器的典型场景:修改一个已经投产中的系统时,需要对系统进行扩展,此时使用一个已有的类,但这个类不符合系统中的接口,这时使用适配器模式是最合适的,它可以将不符合系统接口的类进行转换,转换成符合系统接口的、可以使用的类。
1#include 将抽象和实现解耦,使得两者可以独立地变化 ■抽象和实现分离是桥梁模式的主要特点,是为了解决继承的缺点而提出的设计模式。在该模式下,实现可以不受抽象的约束,不用绑定在一个固定的抽象层次上。 ■实现对客户透明,客户端不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装。 ■提高灵活性和扩展性。 ■如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。 ■设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。 ■一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。 1#include ■高层模块调用简单。一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,即高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。 ■节点自由增加。使用组合模式后,如果想增加一个树枝节点、树叶节点只需要找到其父节点即可。 ■不易控制树枝构件的类型; ■不易使用继承的方法来增加新的行为。 ■需要描述对象的部分和整体的等级结构,如树形菜单、文件和文件夹管理。 ■需要客户端忽略个体构件和组合构件的区别,平等对待所有的构件。组合模式也是应用广泛的一种设计模式。 8.5c++源码实例 ■装饰类和被装饰类可以独立发展,而不会相互耦合。即Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。 ■装饰模式是继承关系的一个替代方案。装饰类Decorator,不管装饰多少层,返回的对象还是Component。、 ■装饰模式可以动态地扩展一个实现类的功能。 多层的装饰是比较复杂的。 ■需要扩展一个类的功能,或给一个类增加附加功能。 ■需要动态地给一个对象增加功能,这些功能可以再动态地撤销。 ■需要为一批类进行改装或加装功能。装饰模式是对继承的有力补充。单纯使用继承时,在一些情况下就会增加很多子类,而且灵活性差,维护也不容易。装饰模式可以替代继承,解决类膨胀的问题 1#include 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得子系统更易使用。 ■减少系统的相互依赖,所有的依赖都是对Faade对象的依赖,与子系统无关。 ■提高灵活性,不管子系统内部如何变化,只要不影响Facade对象,任何活动都是自由的。 ■提高安全性,Facade中未提供的方法,外界就无法访问,提高系统的安全性。 注意外观模式最大的缺点是不符合开闭原则,对修改关闭,对扩展开放。 ■为一个复杂的模块或子系统提供一个供外界访问的接口。 ■子系统相对独立,外界对子系统的访问只要黑箱操作即可。 ■预防风险扩散,使用Faade进行访问操作控制。 1#include ■内部状态是存储在享元对象内部的、可以共享的信息,并且不会随环境改变而改变。 ■外部状态是随环境改变而改变且不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。 享元模式的优点在于大幅减少内存中对象的数量,降低程序内存的占用,提高性能。 但是,相应付出的代价也很高。 ■享元模式增加了系统的复杂性,需要分出外部状态和内部状态,而且内部状态具有固化特性,不应该随外部状态改变而改变,这使得程序的逻辑复杂化。 ■系统中有大量的相似对象,这些对象耗费大量的内存。 ■细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,即对象没有特定身份。 ■需要缓冲池的场景。 按照使用目的可以将代理划分为以下几种。 ■远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局部代表对象。这个不同的地址空间可以是在本机器中,也可以在另一台机器中。 ■虚拟(Virtual)代理:有时需要创建一些消耗较多资源的对象,可以首先创建代理对象,而将真实对象的创建延迟。例如,加载一个很大的图片,可以通过图片的代理来代替真正的图片。 ■保护(ProtectorAccess)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。 ■缓存(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。 ■同步(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。 ■智能引用(SmartReference)代理:当一个对象被引用时,提供一些额外的操作,例如,记录访问的流量和次数等。 ■职责清晰:真实的角色实现实际的业务逻辑,不用关心其他非本职的事务,通过后期的代理完成附加的事务,附带的结果就是编程简洁清晰。 ■高扩展性:具体主题角色随需求不同可能有很多种,但只要实现了接口,代理类就完全可以在不做任何修改的情况下代理各种真实主题角色。 ■智能化:代理类可以在运行时才确定需要去代理的真实主题,这是一种强大的功能。 给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子 ■简单的语法分析工具。 ■扩展性,修改语法规则只要修改相应的非终结符表达式即可,若扩展语法,则只要增加非终结符类即可。 ■解释器模式会引起类膨胀。每个语法都要产生一个非终结符表达式,语法比较复杂时就可能产生大量的类文件,不易维护。 ■采用递归调用方法。每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,不易调试且影响效率。 ■重复发生的问题可以使用解释器模式。例如,多个应用服务器,每天产生大量的日志,需要对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,非终结符表达式就需要制定。 ■一个简单语法需要解释的场景。 ■封装不变的部分,扩展可变部分。不变的部分封装到父类中实现,而可变的部分则可以通过继承进行扩展。 ■提取公共部分代码,便于维护。将公共部分的代码抽取出来放在父类中,维护时只需要修改父类中的代码。 ■行为由父类控制,子类实现。模板方法模式中的基本方法是由子类实现的,因此子类可以通过扩展增加相应的功能,符合开闭原则。 ■多个子类有公共方法,并且逻辑基本相同时。 ■责任链模式将请求和处理分开,请求者不知道是谁处理的,处理者可以不用知道请求的全貌。 ■提高系统的灵活性。 ■降低程序的性能,每个请求都是从链头遍历到链尾,当链比较长的时候,性能会大幅下降。 ■不易于调试,由于采用了类似递归的方式,调试的时候逻辑比较复杂。注意责任链中的节点数量需要控制,避免出现超长链的情况,这就需要设置一个最大的节点数量,超过则不允许增加节点,避免无意识地破坏系统性能。 ■一个请求需要一系列的处理工作。 ■业务流的处理,例如,文件审批。 ■对系统进行补充扩展。 1#include ■类间解耦。调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需要调用Command中的execute()方法即可,不需要了解是哪个接收者执行。 ■可扩展性。Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。 ■命令模式结合其他模式会更优秀。命令模式可以结合责任链模式,实现命令族解析任务,结合模板方法模式,则可以减少Command子类的膨胀问题。 ■使用命令模式可能会导致系统中出现过多的具体命令类,因此需要在项目中慎重考虑使用。 ■使用命令模式作为“回调”在面向对象系统中的替代。“回调”讲的便是将一个函数登记上,然后在以后调用此函数。 ■系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销时,可以调用undo()方法,将命令所产生的效果撤销。 ■需要将系统中所有的数据更新操作保存到日志里,以便在系统崩溃时,可以根据日志读回所有的数据更新命令,重新调用execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。 ■一个系统需要支持交易(transaction)。一个交易结构封装了一组数据更新命令。使用命令模式来实现交易结构可以使系统增加新的交易类型 ■它支持以不同的方式遍历一个聚合对象。 ■迭代器简化了聚合类。 ■在同一个聚合上可以有多个遍历。 ■在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。 ■由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。 ■访问一个聚合对象的内容而无须暴露它的内部表示。 ■需要为聚合对象提供多种遍历方式。 ■为遍历不同的聚合结构提供一个统一的接口。 ■减少类间的依赖,将原有的一对多的依赖变成一对一的依赖,使得对象之间的关系更易维护和理解。 ■避免同事对象之间过度耦合,同事类只依赖于中介者,使同事类更易被复用,中介类和同事类可以相对独立地演化。 ■中介者模式将对象的行为和协作抽象化,将对象在小尺度的行为上与其他对象的相互作用分开处理。 ■中介者模式降低了同事对象的复杂性,但增加了中介者类的复杂性。 ■中介者类经常充满了各个具体同事类的关系协调代码,这种代码是不能复用的。 ■提供一个可回滚的操作。 ■需要监控副本的场景。例如,监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般做法是备份一个主线程中的对象,然后由分析程序来分析。 ■数据库连接的事务管理使用的就是备忘录模式 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新 ■观察者和被观察者之间是抽象耦合。被观察者角色所知道的只是一个具体观察者集合,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体的观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密的耦合在一起,因此它们可以属于不同的抽象化层次,且都非常容易扩展。 ■支持广播通信。被观察者会向所有登记过的观察者发出通知,这就是一个触发机制,形成一个触发链。 ■如果在主题之间有循环依赖,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式时要特别注意这一点。 ■如果对观察者的通知是通过另外的线程进行异步投递,系统必须保证投递的顺序执行。 ■虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有提供相应的机制使观察者知道所观察的对象是如何发生变化。 ■关联行为场景。 ■事件多级触发场景 ■跨系统的消息交换场景,如消息队列的处理机制 ■结构清晰。 ■遵循设计原则。 ■封装性非常好。 ■子类太多,不易管理。 ■对象的行为依赖于它所处的状态,即行为随状态改变而改变的场景。 ■对象在某个方法里依赖于一重或多重条件分支语句,此时可以使用状态模式将分支语句中的每一个分支都包装到一个单独的类中,使得这些条件分支语句能够以类的方式独立存在和演化。如此,维护这些独立的类就不再影响到系统的其他部分。 1#include ■策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为,如果不用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样算法或行为的使用者就和算法本身混在一起,从而不可能再独立演化。 ■使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,这比使用继承的办法还要原始和落后。 ■客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类,即策略模式只适用于客户端知道所有的算法或行为的情况。 ■策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保持到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。可以使用享元模式来减少对象的数量 ■多个类只是在算法或行为上稍有不同的场景。 ■算法需要自由切换的场景。 ■需要屏蔽算法规则的场景。 ■访问者模式使得增加新的操作变得很容易,增加新的操作只需要增加新的访问者类。 ■访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个元素类中。 ■访问者模式可以跨过几个类的等级结构访问属于不同等级结构的成员类。 ■增加新的元素类变得很困难。每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。 ■破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这隐含了一个对所有元素对象的要求,即必须暴露一些自己的操作和内部状态,否则访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使得这些状态不再存储在元素对象中,破坏了类的封装性。 ■违背了依赖倒置原则。访问者依赖的是具体的元素,而不是抽象的元素,这破坏了依赖倒置的原则,特别是在面向对象的编程中,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。 ■一个对象结构包含很多类对象,它们有不同的接口,当对这些对象实施依赖于具体类的操作时,即使用迭代器模式不能胜任的场景下,可以采用访问者模式。 ■业务规则要求遍历多个不同的对象,这本身也是访问者模式的出发点,迭代器模式只能访问同类或同接口的数据,而访问者模式是对迭代器模式的扩充,可以遍历不同的对象,执行不同的操作。