重构关键技法
- 静态到动态
- 早绑定到晚绑定
- 继承到组合
- 编译时依赖到运行时依赖
- 紧耦合到松耦合
设计原则
变化是复用的天敌。
面向对象与软件设计的目的:
- 隔离变化:将变化带来的影响降为最小
- 微观层面:各司其职,新产生的类不影响已存在的类
对象的作用:
- 语言层面:封装了数据和代码
- 规格层面:一系列可被使用的公共接口
- 概念层面:拥有某种责任的抽象
依赖倒置原则DIP
-
高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。
-
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
这两换句话在上述Window的例子中的体现:
- Window不应该因为各个形状(Line等)的变化,而需要调整自身,Window和Line等都转而依赖于抽象的Shape
- 在Shape中不应该使用具体的(Line等)的操作,而是让Line等自己负责自身的实现细节(draw)
开放封闭原则OCP
-
对扩展开放,对更改封闭
-
类模块应该是可扩展的,但不可修改
在Window中,可以扩展更多的形状(Triangle),而不对Window做出更改
单一职责原则SRP
- 一个类应该仅有一个引起它变化的原因
- 变化的方向隐含着类的责任
Liskov替换原则LSP
- 子类必须能够替换他们的基类(IS-A)
- 继承表达类型抽象
接口隔离原则ISP
-
不应该强迫客户程序依赖它们不用的方法
-
接口应该小而完备
只public必要的,仅子类用的就protected,仅自身使用就private
优先使用对象组合而不是类继承
- 类继承通常为白箱复用,对象组合通常为黑箱复用
- 继承在某种程度上破坏了封装性,子类父类耦合度高继承有时候导致父类对子类暴露的东西过多
- 而对象组合只要求被组合的对象具有良好的接口,耦合程度低
封装变化点
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良影响,从而实现层次之间的松耦合
针对接口编程而不是针对实现编程
-
不将变量类声明为某个特定的具体类,而是声明为某个接口
-
客户程序无需获知对象的具体类型,只需知道对象所具有的接口
-
减少系统中各部分的依赖关系,从而实现 高内聚,松耦合 的类型设计方案
Window的实现中,分解的实现包含了具体类的vector,而抽象实现使用Shape*
单例模式-Singleton
单例
单例模式保证某个类只会创建一个实例,当某些类创建对象时,内存开销过大或者时间过长,可以考虑使用这个模式。注意:这个只创建一个类你不能由用户去保证,你应该自己保证这个类只会有一个实例。
要点总结:
非常简单,static原有的特性。除了static,还有很多可以实现单例模式,比如unique_ptr,call_one等方式。
代码:
饿汉式单例模式:
不管是否需要,都会在类加载的时候创建一个实例。
因为是在类加载的时候创建实例,所以在后续程序中不会再涉及到类创建,所以它是线程安全的。
懒汉式单例模式:
只有到需要的时候,手动建立实例,它不是线程安全的,需要连同锁的机制一起使用。
模板方法模式-Template Method
模板方法
定义一个操作中的算法的骨架(稳定),而将一些步骤的实现延迟(变化)到子类中。Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤。
解决什么问题/strong>
比如说多个业务,它们只有一部分操作不相同,比如说只有A操作不同,而A之前或者A之后都是相同的,如果为每一个业务都重写一个类,就会有大量的代码重复,这是不好的,所以我们将相同的地方构造成模板来复用。
违背了什么原则/strong>
它似乎没有违背什么原则,但是它违背了设计模式的初衷:代码复用。
要点总结:
- 一个抽象类,定义骨架流程(抽象方法放一起)
- 确定的共同方法步骤,放到抽象类(去除抽象方法标记)
- 不确定的步骤,给子类去差异化实现
模板方法就是C++的虚函数多态机制实现的运行时动态绑定,只不过稳定的算法框架是在基类中固定,变化的部分用虚函数封装,来实现子类的动态绑定。
早绑定与晚绑定
区分稳定与变化
代码:
策略模式-Strategy
策略
定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换(变化)。该模式使得算法可以独立于使用它的客户程序(稳定)而变化(扩展、子类化)。
解决什么问题/strong>
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,会使得对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。就是说,你的代码中会随着业务类型的增加,而增加大量的if 分支语句,使得原代码臃肿。
如何在运行时根据需求更透明的更改对象的算法/p>
将算法与对象本身解耦,从而避免上述问题/p>
违背了什么原则/strong>
- 开闭原则 (对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码
- 单一原则 (规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。
每增加一个职责,就增加一个if else分支,让一个类负责所有的算法功能,这就是违背了单一职责原则。应该把每个算法独立封装称类,它们共同继承方法基类。
每次增加职责,就要修改源代码,这就是违背开闭原则,严格禁止对现有代码进行大量修改,应支持代码拓展。
要点总结:
- Strategy及其子类为组件提供了一系列可重用的算法,从而使得类型在运行时方便的根据需要在各个算法之间切换
- Strategy提供了判断语句外的另一种选择
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省开销
代码:
没有使用策略模式:
使用策略模式:
责任链模式-Chain of Resposibility
责任链
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
解决什么问题/strong>
在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少地带来请求发送者与接受着的紧耦合。
如何使请求的发送者不需要指定具体的接受者请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用 责任链模式 。
责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
要点总结:
- Chain of Responsibility 模式的应用场合在于“一个请求可能有多个接受者,但最后真正的接受者只有一个”,这时候请求发送者与接受者的耦合有可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦,从而更好地应对变化。
- 应用了Chain of Responsibility模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理职责。
- 如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。
怎么用/strong>
- 一个接口或者抽象类
- 每个对象差异化处理
- 对象链(数组或者链表)初始化(连起来)
代码:
观察者模式-Observer
观察者
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。类似Qt的信 槽机制,信 ,条件变量等。
解决什么问题/strong>
在软件构件过程中,我们需要为某些对象建立一种”通知依赖关系“,一个对象(目标对象)的状态发送改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于亲密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
要点总结:
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
- 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
- 被观察者(Observerable):目标对象,状态发生变化时,将通知所有的观察者。
- 观察者(observer):接受被观察者的状态变化通知,执行预先定义的业务。
代码:
// 被观察者
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!