GOF设计模式总结
目录
GOF 设计模式
1 从面向对象谈起
1.1 底层思维:向下,如何把握机器底层,从微观理解对象构造
- 语言构造
- 编译转换
- 内存模型
- 运行时机制
1.2 抽象思维:向上如何将我们周围的世界抽象为程序代码
- 面向对象
- 组件封装
- 设计模式
- 架构模式
2 理解面向对象
2.1 向下
深入理解三大面向对象机制
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
示例代码:
# include <iostream>
class Shape {
public:
virtual void draw() {
std::cout << "Drawing a generic shape." << std::endl;
}
};
class Rect : public Shape {
public:
void draw() override {
std::cout << "Drawing a rectangle." << std::endl;
}
};
class Point : public Shape {
public:
void draw() override {
std::cout << "Drawing a point." << std::endl;
}
};
int main() {
Shape* shape1 = new Rect();
Shape* shape2 = new Point();
shape1->draw(); // 多态调用,输出 "Drawing a rectangle."
shape2->draw(); // 多态调用,输出 "Drawing a point."
delete shape1;
delete shape2;
return 0;
}
3 向上
深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计”。
4 软件设计复杂的根本原因
4.1 变化
变化是复用的天敌 建筑商从来不会去想给一栋已建好的 100 层高的楼房底下再新修一个小地下室—这样做花费极大而且注定要失败。 然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。
- 客户需求的变化
- 技术平台的变化
- 开发团队的变化
- 市场环境的变化
- …
5 软件设计的目标
管理变化, 提高复用 复用是好的软件设计的金科玉律。
6 两种手段解决复杂性
6.1 分解
分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
6.1.1 抽象
通过抽象来处理复杂性,忽略非本质细节,处理泛化和理想化的对象模型。
7 面向对象设计原则
7.1 DIP 依赖倒置原则
- 高层模块(稳定)不应该依赖底层模块(变化),两者都应该依赖其抽象(稳定)
- 抽象(稳定)不应该依赖细节(变化),实现细节应该依赖抽象(稳定)
7.2 OCP 开放封闭原则
- 对扩展开放,对修改封闭
- 类模块应该是可扩展的,但是不可修改
7.3 SRP 单一职责原则
- 一个类应该仅有一个引起它变化的原因
- 变化的方向隐含着类的责任
7.4 LSP 里氏替换原则
- 子类必须能够替换它们的基类(IS-A)
- 继承表达类型抽象
7.5 ISP 接口隔离原则
- 不应该强迫客户程序依赖它们不用的方法
- 接口应该小而完备
7.6 对象组合优于继承
- 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”
- 继承在某种程度上破坏了封装性,子类父类耦合度高
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低
7.7 封装变化点
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合
7.8 针对接口编程而不是针对实现编程
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案
- 如果能接口标准化是更好的,以史为鉴
- 活字印刷术
- 秦朝统一度量衡
8 GOF-23 模式分类
8.1 从目的来看
8.1.1 创建型模式(Creational Patterns)
将对象的部分创建工作延迟到子类,使得在运行时刻再确定子类的创建方式, 从而使得系统更加灵活。
8.1.2 结构型模式(Structural Patterns)
通过类继承或对象组合的方式,来实现类或对象的组合,从而达到更灵活的结构。
8.1.3 行为型模式(Behavioral Patterns)
通过类继承或者对象组合,来明确类与对象之间的责任分配,从而实现更灵活的功能。
8.2 从范围来看
8.2.1 类模式(Class Patterns)
类模式处理类与子类的静态关系
8.2.2 对象模式(Object Patterns)
对象模式处理对象间的动态关系
8.3 从封装变化的角度对模式分类
8.3.1 组件协作
- template method
- strategy
- observer / event
8.3.2 单一职责
- decorator
- bridge
8.3.3 对象创建
- factory method
- abstract factory
- prototype
- builder
8.3.4 对象性能
- singleton
- flyweight
8.3.5 接口隔离
- facade
- proxy
- mediator
- adapter
8.3.6 状态变化
- memento
- state
8.3.7 数据结构
- composite
- iterator
- chain of responsibility
8.3.8 行为变化
- command
- visitor
8.3.9 领域问题
- interpreter
9 重构与模式(refactoring to patterns)
- 面向对象设计模式是“好的面向对象设计” 指是那些可以满足 “应对变化,提高复用”的设计
- 建议寻找变化点,然后在变化点处应用设计模式
- 我反对过度设计,但是我更反对过度重构,重构的目的是为了让代码更加灵活,而不是为了使用设计模式而使用设计模式
- 设计模式的应用不宜先入为主,而应该在实际的项目中,根据实际的需求,来选择合适的设计模式。切记没有一步到位的设计模式
- 分辨哪些稳定,哪些变化很重要!
10 重构的关键技巧
- 静态-> 动态
- 早绑定-> 晚绑定
- 继承-> 组合
- 编译时依赖-> 运行时依赖
- 紧耦合-> 松耦合
11 tips
随着时代的发展, 有些面向对象的设计模式已经使用的很少了, 比如以下几种, 但是思想是值得学习的
- builder
- mediator
- memento
- iterator
- chain of responsibility
- command
- visitor
- interpreter
比如 iterator 在 c++ 不常用, 一般使用 stl 的模版的迭代器来替换这种面向对象的迭代器, 这种模版的编译时多态运行效率更高
12 什么时候不适用设计模式
12.1 原则
- 代码可读性很差时
- 需求理解还很浅时
- 变化没有显现时
- 不是系统的关键依赖点
- 项目没有复用价值时
- 项目将要发布时
12.2 经验
- 不要为模式而模式
- 关注抽象类&接口
- 理清变化点和稳定点
- 审视依赖关系
- 要有 Framework 和 Application 的区隔思维
- 良好的设计是演化的结果