设计模式对于软件设计的重要性不言而喻,每一个c++程序员都应该学习,但是设计模式又不是那种死记硬背的知识,更多的是需要我们能够在软件开发中磨炼;如果说设计模式中23种设计模式是术,那么六项基本原则就是道,术固然重要,但是更重要的是我们要从术中领悟道的存在;
六大原则
- 开放封闭原则:对修改封闭,对扩展开放;例如使用抽象类派生扩展功能;
- 单一职责原则:一个类只做一件事,不要让一个类耦合过多的功能;
- 里氏替换原则:子类应当可以完全替换父类;里氏替换原则其实本质上是一种is-a关系,如果违背了is-a关系,那么也将违背里氏替换原则,例如:鸵鸟继承鸟,但是鸵鸟并不会飞,这其实就是一种不良的父子关系;除了子类替换父类,子类与子类之间也可以相互替换,如果我们在父类中增加所有子类的扩展接口,那么就能实现这种插拔式实现;
- 依赖倒置原则:细节应该依赖于抽象,抽象不应依赖于细节;如果两个类具有依赖关系,那么最好两个类都是抽象类,如果无法做到,那么至少要有一个类是抽象类,其中抽象类被依赖;
- 迪米特法则:又叫最少知道原则,如果两个类不必彼此直接通信,那么这两个类就不应当直接发生相互作用,而应当通过第三个类来转发这个调用;如果两个类直接发生相互作用,那么被调用的类实际上可以访问调用类的私有成员,这对于封装性而言是个威胁;
- 接口隔离原则:如果一个类被另一个类通过接口依赖,而该接口功能过于冗余,那么应当将接口拆分,让两个类通过最小接口依赖;
设计模式
构建型模式
工厂模式
工厂模式又分为:简单工厂模式、工厂方法模式、抽象工厂模式;
简单工厂模式:最常用方法是传入枚举,然后返回对应的产品;这种模式的缺陷在于,如果需要增加新的产品,就需要修改工厂,违背了开放封闭原则;
工厂方法模式:工厂方法模式为了弥补简单工厂模式的不足,为每一种产品增加一个独立的工厂,如果要增加新的产品,我们只需要增加新的工厂类即可;工厂方法模式的优势在于,我们只需要在创建时指定特定工厂,后面只需要跟抽象工厂类交互即可,而不需要知道具体是哪个工厂;
抽象工厂模式:抽象工厂和工厂方法模式的唯一区别在于:抽象工厂是面对的一个产品族;即每个工厂,并不只生产一种产品,而是多种产品;这种模式存在的缺陷在于:如果要增加一种产品,那么每个工厂都要增加相应的方法接口;
单例模式
适用于全局只有一个实例对象,如果不存在,则创建;
建造型模式
- product:需要创建的产品,但其有一定的生产流程;
- builder:用于实现产品生产中的各种生产的具体步骤;
- director:用于组织产品生产步骤的顺序;
通常而言,不同产品,对应不同builder,builder中创建步骤也不相同;
建造型模式适用于有多种产品,这些产品的生产步骤顺序相同,但生产步骤中的具体实现不同;
原型模式
简而言之,就是clone函数的实现,实现深拷贝;
结构型模式
结构型模式是用来设计程序结构的;
适配器模式
适配器模式相对简单,例如:220V电压需要转换到5V我们只需要通过接口重新封装就能实现;但是程序中不建议过多采用这种设计模式,它通常意味着我们的程序存在接口设计方面的缺陷;
桥接模式
桥接模式其实描述的是一种合成 / 聚合关系,如果两个属性是平行关系,那么优先使用桥接模式,例如:图形与颜色,这两种属性可以独立变化,如果使用继承,例如红色矩形,黄色矩形,那么派生类将会过于庞大,那么我们可以使用抽象图形类包含抽象颜色类,颜色通过外部设置,那么就可以进行颜色与图形的自由组合;
桥接模式体现了合/聚合原则:尽量使用合成/聚合,而不要使用类继承;因为类继承泛化将会使代码规模越来越庞大;
合成聚合示意图如下:
聚合表示的是一种弱拥有关系,而合成则是一种强拥有关系;
组合模式
组合模式,适用于整体与部分具有相似的结构,例如文件夹的包含关系;这在很多的第三方库中都有运用,例如osg中的节点关系,所有的不同类型的节点派生于抽象节点类,抽象节点类实现addNode和removeNode;组合模式比较重要的是需要注意其有两种模式:安全与透明模式;安全模式是针对不同子类提供不同接口;透明模式是所有子类接口都在基类中定义;通常而言,透明模式更受欢迎;
装饰模式
装饰模式主要用于1.增强功能;2.增加功能;其主要实现方式是:让装饰类与要修饰的类继承于同一基类,那么,我们在装饰类中实现基类接口时同时调用要装饰的类的接口,就可以起到增强功能。
例如,当我们在商店购买商品时,任何商品都需要实现cost函数,那么我们就可以使用装饰模式,进行商品的层层包装,然后价格累计即可;如果要增加功能,同样可以这样,在装饰类中实现被装饰类中没有的接口,那么被装饰类的接口就得到了扩展,这种方式可以替代基类派生的扩展,也符合合成/聚合原则;严格来讲,装饰模式违背了里氏替换原则,但是其设计却很巧妙;
外观模式
例如下班,我们可能会有一系列的动作:关浏览器、关电脑、打卡,我们可以将这一系列的动作分别作为接口,也可以把一系列的动作封装在下班这个接口里,外观模式降低了耦合度;体现了迪米特法则;
享元模式
享元模式是较为难理解的一种模式,其把一个要创建的对象分成两种属性,一种内部不可变,一种外部可变,外部可变可由外部设置,内部不可变保持不变;该对象由工厂创建,工厂管理一个hash表,当已存在时,直接返回,不存在时,则创建,并加入表,当需要使用时通过key找到相应对象进行调用;从而降低重复的内存分配;
这里需要注意的是:UnsharedConcreteFlyweight主要用于不可共享的对象;尽管大部分时候都需要共享内存降低内存消耗,但是也不能避免存在不需要共享的对象;
代理模式
代理模式,代理和被代理的对象应当具有继承同一基类,这样可以保证,任何想使用代理对象的时候,都可以使用代理,代理对象本身并不重要,我们甚至可以直接在代理中创建代理对象;
代理模式隐的应用场景主要包括以下几个方面:
- 远程代理,为一个对象在远程空间提供局部代表;
- 虚拟代理,例如浏览器中延迟加载图片的实现;
- 安全代理,用于控制对象访问时的权限,权限由代理控制;
- 智能指引,调用真实对象时,代理处理另外一些事,比如计算引用次数;
行为型模式
行为型模式关注于类与类之间的交互与协作;
责任链模式
比如请假,请1天我们只需要小组长批准;请1周需要部门经理批准;请1个月需要总监批准;那么这种情况就可以使用责任链模式表达出来,对于存在于该责任链上的类,我们只需要完成两个动作:如果能够处理,那么就处理;如果不能处理,那么由后继者处理;所以每个对象都应知道其后继者;
命令模式
如果我们去饭店吃饭,我们是client,中间起到交互作用的是waiter,最后执行的是厨师,对于命令模式,可以将这种场景抽象化。command:每个command绑定执行者,当就收命令时,调用执行者执行;invoker:相当于waiter,保存一个command表,在下完订单后通知执行,同时,需要有移除/撤销操作;Receiver:执行command的具体操作;
解释器模式
解释器模式,可以参考正则表达式使用,通过正则表达式定义的规则,我们可以筛选不同类型的字符串,在实现时,我们需要针对不同规则,在基类的基础上派生出不同规则类,然后在遍历某一字符串(或者其他文本)时,根据不同类型,实例化解释器,返回解释结果;
迭代器模式
迭代器模式实际使用意义已经不大,因为c++及很多其他语言中已经实现了迭代器遍历,参考迭代器遍历,我们可以自己实现迭代器模式,其要求迭代器具备以下四个接口:begin()、isDone()、next()、currentitem();同时,迭代器类需要知道其所遍历的容器对象;
中介者模式
中介者模式,和桥接模式似乎很像,但和桥接模式还是存在区别,桥接模式作为一种构建型模式,其强调的是类的构建,中介者模式则强调类之间的交互方式,中介者类需要知道所有需要交互的类,然后在交互信息时,实际通过中介者类去交换信息;中介中模式中每个需要通信的对象都可以包含一个中介者对象,然后需要和其他类通信时,直接调用其接口即可;这样就避免了各个对象之间的直接交互;中介者模式体现了迪米特法则;
备忘录模式
备忘录模式,主要针对进度的保存,例如游戏进度;备忘录模式主要分为三部分:role:角色,其状态是需要我们保存的内容,以便可以复原;memo:用于保存role中的部分或全部状态,一般而言,其应该由role创建,从而传入role状态;context:主要用于管理memo,具备set、get方法;
备忘录模式适用于需要保存历史记录的场景,Creataker对象主要用于存储备忘录,而不提供任何其他操作,如果由多个历史记录,我们也可以使用其进行管理;
观察者模式
观察者模式,是最常用的设计模式之一,其用于:当一个对象状态发生变化时,其他相应状态也发生变化;其主要由观察者、被观察者,被观察者在适当的时候通知观者者状态变化;贯彻者模式体现了依赖倒置原则;
状态模式
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的逻辑简化;
Satae对象与Context中的特定状态相对应,当符合条件时,State即执行操作,不符合条件时,修改当前状态,执行下一操作;因此,每一个状态都应知道下一状态从而设置状态,类似职责链模式中的next;
策略模式
策略模式,主要分为两个部分:context类,用于配置算法对象,同时为上层调用配置一个统一的接口;算法类,用于实现不同的算法;当我们碰到需要不同算法的场景时,就可以使用这种策略模式;
策略模式可以和简单工厂模式配合使用,可以使客户端完全解耦具体的算法,从而达到更好的效果;
模板方法模式
模板方法,适用于过程固定,但是某些细节不同的情形下,这样,我们可以把过程放在基类中,而将具体实现延迟到派生类中实现即可;这种模式与建造者模式类似;
访问者模式
访问者模式是23中设计模式中最复杂的一种,其适用于数据结构稳定,但是算法变化的情形,数据结构稳定,那么我们就可以在算法类中提供固定的几种接口,然后为数据结构对应类中提供accept函数,这种通过二次分派:第一次将状态传入数据结构类,第二次,将数据结构又传入状态类中从而完成二次分派;
访问者模式适用于系统有比较稳定的数据结构,又有易于变化的算法;它把数据结构和u总用于结构上的操作之间的耦合解脱,使操作集合可独立演化;