目录
设计模式目标 — 可复用
设计模式假设条件 — 存在稳定点
设计模式真谛 — 编译时复用,运行时变化。
思维方式 | 作用 | 内容 |
---|---|---|
底层思维 | 把握机器底层微观构造 | 语言构造、编译转换、内存模型、运行机制 |
抽象思维 | 将现实世界抽象为程序代码 | 面向对象、组件封装、设计模式、架构模式 |
面向对象
-
向下
-
封装 — 隐藏内部代码
-
继承 — 复用已有代码
-
多态 — 改写对象行为
-
-
向上
- 深刻把握面向对象机制带来的抽象意义,理解如何利用这些机制来表达现实世界。
软件设计复杂原因
软件设计复杂性根本原因 — 变化(客户需求、技术平台、开发团队、市场)
解决复杂性
-
分解 — 分而治之,大问题分解为多个小问题,复杂问题分解为多个简单问题(独立实现每个小类,分别实现相应的功能)
-
抽象 — 由于不能掌握全部复杂对象,选择忽视一些非本质的细节。而去处理泛化和理想化的对象模型。(抽象一个虚基类,每个具体小类继承并重写)
C++对象模型
几乎所有的设计模式都采用类内部组合一个对象指针的形式(指针指向多态对象以解耦合)
什么时候不用设计模式
- 代码可读性差
- 需求理解很浅
- 变化尚未显现
- 不是系统关键依赖点
- 项目无复用价值
- 项目将要发布
经验之谈
- 不要为了模式而模式
- 关注抽象类和接口
- 理清变化点和稳定点
- 审视依赖关系
- 要有框架和应用的区隔思维
- 良好的设计是演化的结果
设计模式成长之路
- 「手中无剑,心中无剑」 — 见模式而不知
- 「手中有剑,心中无剑」 — 可以识别模式,作为应用开发人员使用模式
- 「手中有剑,心中有剑」 — 作为框架开发人员为应用设计模式
- 「手中无剑,心中有剑」 — 忘掉模式,只有原则
面向对象设计原则
面向对象设计最大优势 — 抵御变化
面向对象
- 隔离变化 — 面向对象构建方式更能适应软件变化,能将变化带来的影响降到最小(宏观)
- 各司其职 — 需求变化导致的新增类型,不影响原来类型的实现(微观)
对象
- 语言层面 — 对象封装了代码和数据
- 规格层面 — 对象定义了一系列接口
- 概念层面 — 对象是拥有某种责任的抽象
设计原则
-
依赖倒置原则(DIP)
-
高层模块(稳定)不应该依赖于低层模块(变化),二者均依赖于抽象(稳定)。
-
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
-
-
开放封闭原则(OCP)
-
对扩展开放,对更改封闭
-
类模块应该是可扩展的,但不可修改。
-
-
单一职责原则(SRP)
- 一个类应该仅有一个引起变化的原因
- 变化的方向隐含类的责任
-
Liskov替换原则(LSP)
- 子类必须能够替换基类(子类能调用父类方法)
- 继承表达类型抽象
-
接口隔离原则(ISP)
- 不应该强迫客户程序(使用者)依赖不用的方法
- 接口应该小而完备
-
优先使用对象组合,而不是类继承
- 类继承通常为「白盒复用」,对象组合通常为「黑盒复用」
- 继承在某种程度上破坏了封装性,耦合度高。而对象组合则要求被组合的对象具有良好定义的接口,耦合度低。
-
封装变化点
- 使用封装创建对象之间的分界层,让设计者可以在其一侧修改,不会对另一侧产生不良影响。实现层次间的松耦合
-
针对接口编程,而非针对实现。
- 不将变量类型声明为具体类,而是声明为接口。
- 客户程序无需知晓对象的具体类型,只需要知道所具有的接口。
产业强盛标志 — 接口标准化
设计经验
由设计原则归纳、总结出的点
-
设计习语(Design Idioms) — 与特定编程语言相关的底层模式、技巧惯用法
-
设计模式(Design Patterns) — 类与对象之间的组织关系,包括角色、职责、协作方式
-
架构模式(Architectural Patterns) — 系统中与组织结构关系密切的高层模式,包括子系统划分、职责、组织关系。
设计模式分类
23个设计模式的分类原则
-
目的
-
创建型 — 对象创建
-
结构型 — 对象需求变化对结构造成的冲击
-
行为型 — 多个类交互
-
-
范围
- 类模式 — 处理类与子类的静态关系(继承)
- 对象模式 — 对象间的动态关系(组合)
-
封装变化
- 组件协作 —
Template Method、Strategy、Observer/Event
- 单一职责 —
Decorator、Bridge
- 对象创建 —
Factory Method、Abstract Factory、Prototype、Builder
- 对象性能 —
Singleton、Flyweight
- 接口隔离 —
Facade、Proxy、Mediator、Adapter
- 状态变化 —
Memento、State
- 数据结构 —
Composite、Iterator、Chain of Resposibility
- 行为变化 —
Command、Visitor
- 领域问题 —
Interpreter
- 组件协作 —
由于时代的发展,一些设计模式已不常用:
Builder、Mediator、Memento、Iterator、Chain of Resposibility、Command、Interpreter、Visitor
Refactoring to Patterns
重构获得模式
是普遍认为最好的使用设计模式方法
- 面向对象设计模式可以应对变化、提高复用。
- 现代软件设计特征 — 需求频繁变化。
- 设计模式的要点 — 寻找变化点(将稳定部分和变化不分分离开),变化点处使用设计模式来应对变化
- 设计模式的应用不该先入为主(防止误用)。没有一步到位的设计模式。(故,要
Refactoring to Patterns
)
步骤
- 自主增加相应的模块
- 思考违背哪些设计原则
- 重构代码
重构
- 静态绑定 → \to →动态绑定
- 早绑定 → \to →晚绑定
- 继承 → \to →组合
- 编译时依赖 → \to →运行时依赖
- 紧耦合 → \to →松耦合
GoF23
组件协作
通过晚期绑定,实现框架与应用程序之间的松耦合。实现「框架与应用程序之间的划分」
Template Method
模板方法。定义一个操作中的算法的骨架(稳定),将一些步骤延迟(变化)到子类。使得子类可以不改变一个算法的结构(复用),同时重定义该算法的某些特定步骤。
动机
软件构造过程中,对于某项任务,有稳定的整体操作结构,但各个子步骤却有很多改变的需求;或者由于固有原因而无法和任务整体结构同时实现。
模板方法能够在稳定操作的前提下,灵活应对各个子步骤的变化及晚期实现需求。
AbstractClass
— 稳定的流程ConcreteClass
— 实现时会变化的步骤
要点
Template Method
是非常常用的基础设计模式,面向对象系统中大量使用。Template Method
机制简洁(虚函数的重载),为许多应用程序架构提供了灵活扩展点,是代码复用层面的基本实现结构。Template Method
内含反向控制结构(App
调用Lib
中的方法 → \to →Lib
调用App
重写的方法)Template Method
调用的虚方法可以不做实现,但一般设计为protected
方法。(流程中的一部分,不供外界调用)
样例
基类(Lib)实现执行流程,关于具体细节部分(步骤的详情),通过相应的派生类(App)去重写。
步骤的具体功能改变,不需要重写框架中的执行流程
//库
class Library{
public:
//具体执行流程(定)
void Run() {
Step1();
if(Step2()) {
Step3();
}
}
virtual ~Library() {
...}
protected:
// 定
void Step1() {
...}
void Step3() {
...}
// 变
virtual bool