前言
本文设计模式代码分为两个版本,具体代码在设计模式代码,其中1.cpp表示有问题的版本,2.cpp表示使用完设计模式之后的版本。对比分析看一下效果会更好。
设计模式总览
设计模式是什么?
设计模式是在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。总结来说,它是一种解决问题的固定套路,我们要慎用设计模式。
设计模式是怎么来的?
满足设计原则后,慢慢迭代出来的。这就要求我们要先写出满足设计原则的代码,后面就可以修改少量的代码来形成相应的设计模式。
设计模式使用前提
具体需求既有稳定点,又有变化点
设计模式解决了什么问题?
期望修改少量的代码,就可以适应需求的变化(比喻:整洁的房间,好动的猫,怎么保证房间的整洁?把猫关在一个笼子里(即把变化控制在一小块范围内))
怎么学习设计模式?
- 找稳定点和变化点,把变化点隔离出来
对于稳定点,用抽象去解决;把变化点隔离出来就是在解耦合的过程,当我们面对变化点的时候,往往需要以扩展代码的方式(即组合基类的指针或者继承)来应对,这样即使后面需要变化了,也只需要改少量的代码。 - 我们写的代码要先满足设计原则,慢慢迭代出设计模式
当我们不知道业务的需求会往哪些方面去扩展的时候,我们只要写出符合设计原则的代码就行了,这样即使后面需求会确定往哪方面发展的时候,我们只需要改变一小部分代码就可以将其转换为对应的设计模式
如何理解并深刻记忆一个设计模式?
记其中的稳定点和变化点,这样才能记忆深刻
设计原则
依赖倒置原则
定义:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖具体实现,具体实现应该依赖于抽象;
解析:定义一个抽象,即接口,具体实现依赖于这个接口做具体实现,用户(指拿着你这个具体的接口去实现具体的功能)也要依赖这个接口去实现具体的功能,这个接口就解决了具体实现与用户的解耦;高层模块不应该依赖于低层模块(即用户或者说接口的使用者不应该依赖于具体的实现),而两者都应该依赖于抽象,这样的话高层的变化和低层的变化就解耦了。
开放封闭原则
定义:一个类应该对扩展(组合和继承)开放,对修改关闭;
例如,组合的是一个Base对象,内存分布是紧挨着一起的,说明它们的耦合度很高,从内存上来看就是紧密的,不容易变更
组合一个基类的指针,就可以有很多子类继承自这个Base类,子类的传递给这个base指针,这个父类就可以呈现出相对应的功能,体现出一个扩展性,也就是晚绑定,区别在于这是一个松耦合,可扩展特性,也就是晚绑定,尽量用这种方法组合基类指针
虚函数继承的体现,虚函数指针会指向虚函数表,用虚函数的扩展方式
总结:扩展的方式有两种,组合基类的指针,继承虚函数覆盖
面向接口
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口;
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案;
封装变化点
- 将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点的实现层次分离;
单一职责
- 一个类应该仅有一个引起它变化的原因,如果有多个变化的原因,就会变得不稳定(结合template1.cpp的_type成员和template2.cpp来理解)
里氏替换
- 子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可能出现错误;覆盖了父类方法却没有实现父类方法的职责;(结合template2.cpp的几个show0函数来理解)
接口隔离
- 不应该强迫客户依赖于它们不用的方法;
- 一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责;
- 客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。
说明:接口隔离从代码实现上一般分为两种:
- 一个是类与类之间,通过接口去隔离;
- 还有一个是类封装的本身,我们通过限定词private这种去进行隔离
组合优于继承
- 继承耦合度高,组合耦合度低;
常用的设计模式
模板方法模式
要点:
- 最常用的设计模式,子类可以复写父类子流程,使父类的骨架流程丰富;
- 反向控制流程的典型应用;
- 父类 protected 保护子类需要复写的子流程;这样子类的子流程只能父类来调用;
说明:
模板方法为什么符合依赖倒置原则?
因为子类扩展时,需要依赖基类的虚函数实现(即依赖基类的接口,也即具体实现依赖于抽象(接口));此外使用者只依赖于骨架接口(抽象),并不是依赖于具体的实现。
模板方法为什么符合封装变化点原则?
因为我们这里使用protected关键词来封装变化点,变化的地方让它限定住让子类去扩展。
模板方法为什么符合最小知道原则?
因为对于用户而言,只需要知道那个固定算法的接口即可。
观察者模式
要点:
- 观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合;
- 观察者自己决定是否订阅通知,目标对象并不关注谁订阅了;
- 观察者不要依赖通知顺序,目标对象也不知道通知顺序;
- 常用在基于事件的ui框架中,也是 MVC 的组成部分;
- 常用在分布式系统中、actor框架中;
说明:
应对稳定点“一对多的依赖关系”是通过在"一"中定义一个容器(里面放的是接口或者说抽象)来实现;应对稳定点"一变化多跟着变化"是通过抽象出接口的方式来实现;
我们这里的变化点主要是两个,一个是一对多中"多"增加,另一个是一对多中"多"减少,这里使用attach封装多增加,detach封装多减少。
策略模式
要点:
- 策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换;
- 策略模式消除了条件判断语句;也就是在解耦合;
说明:
- 应对稳定点,采用抽象接口的方式来解决;
- 应对变化点,采用扩展(继承和组合)方式来解决。