一、设计模式是什么
- 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。
- 解决问题的固定套路。
- 慎用设计模式。
二、设计模式是怎么来的
- 满足设计原则后,慢慢迭代出来的。
三、设计模式解决了什么问题
- 前提:具体需求既有稳定点,又有变化点。
- 期望修改少量的代码,就可以适应需求的变化。
- 比喻:整洁的房间,好动的猫,怎么保证房间的整洁 → 把猫关在一个笼子里。
四、设计原则
- 开闭原则:
- 一个类应该对扩展开放,对修改关闭。
- 通过继承和组合的方式进行扩展。
- 与开闭原则相关的原则:单一职责、里氏替换、接口隔离。
- 单一职责:一个类应该仅有一个引起它变化的原因。
- 里氏替换:
- 子类方法重写父类方法时,需要实现父类方法的职责,避免之前调用父类方法的地方出错。
- 场景:多态中虚函数重写。
- 目标:约束扩展中的继承(对扩展开放)。
- 接口隔离:
- 不应该强迫客户依赖它们不用的方法,使用
public
、protected
、private
。 - 一个类对另一个类的依赖应该建立在最小的接口上。
- 不应该强迫客户依赖它们不用的方法,使用
- 迪米特原则(最小知道原则):
- 一个对象应该对其他对象保持最小的了解。
- 目标:形成高内聚、低耦合的系统。
- 相近的功能应该放在同一个类中,不相近的不要放在同一个类中。
- 类与类之间的依赖关系要简单清晰,依赖越少越好。
- 要求:
- 不该有直接依赖关系的类之间不要有依赖。
class NetWork { public: int Write(int fd, unsigned char* buf, size_t sz); int Read(int fd, unsigned char* buf, size_t sz); // int Write(HTTP* pHttp); 耦合度太高 }; class HTTP { private: NetWork* pNet; };
- 有依赖关系的类之间,尽量只依赖必要的接口。
class IEncode { public: unsigned char* encode(HTTP* p) = 0; }; class IDecode { public: json* decode(HTTP* p) = 0; }; class A : public IEncode, IDecode { public: unsigned char* encode(HTTP* p); json* decode(HTTP* p); }; class B : public IEncode { }; class C : public IDecode { };
- 不该有直接依赖关系的类之间不要有依赖。
- 依赖倒置:
- 目标:高内聚、低耦合。
- 高层模块不应该依赖低层模块,两者都应该依赖抽象。
- 抽象不应该依赖具体实现,具体实现应该依赖抽象。
- 抽象确定稳定点。
- 用户使用和代码实现的解耦,也就是接口的使用者不依赖具体的实现。
- 组合优于继承:继承的耦合度高。
五、设计模式学习步骤
- 该设计模式解决了什么问题 → 稳定点、变化点。
- 该设计模式的代码结构是什么。
- 该设计模式符合哪些设计原则。
- 如何在该设计模式上扩展代码。
- 该设计模式有哪些典型应用场景。
六、模板方法
- 解决的问题:
- 稳定点:算法的骨架。
- 变化点:子流程需要变化。
- 符合哪些设计原则:
- 单一职责:只暴露算法的骨架接口,其它的子流程接口都用
protected
或private
封装起来。 - 开闭原则:不能改变算法的骨架,只能修改子流程。
- 依赖倒置。
- 子类扩展时,需要依赖基类的虚函数实现。
- 使用者只依赖接口。
- 封装变化点:变化的地方通过
protected
暴露给子类去扩展。 - 接口隔离:通过
protected
、private
隔离接口。 - 最小知道原则:对于用户而言,只需要知道算法的骨架接口即可。
- 单一职责:只暴露算法的骨架接口,其它的子流程接口都用
- 如何扩展:
- 实现子类继承基类,复写子流程。
- 通过多态调用方式使用。
- 代码结构:
- 基类中有骨架流程接口。
- 所有子流程对子类开放(
protected
)并且是虚函数。 - 通过多态调用方式使用。
// 背景:某个动物园,有一套固定的表演流程,但是其中若干个表演子流程可替换,以迭代更新表演流程。 /* * 接口隔离原则:1、类封装,使用权限限定词来实现。 2、类与类依赖,使用接口实现(依赖注入) * 最小知道原则 * 单一职责原则:类封装 * 开闭原则:对扩展开放,对修改关闭 * 里氏替换原则:多态 * * 扩展方式:继承、多态组合 */ using namespace std; class ZooShow { public: void Show() { if (Show0()) { PlayGame(); } Show1(); Show2(); Show3(); } private: void PlayGame() { cout << "after Show0, then play game" << endl; } // 对其它用户关闭,但是对子类开放 protected: bool expired; virtual bool Show0() { cout << "Show0" << endl; if (!expired) { return true; } return false; } virtual void Show1() { cout << "Show1" << endl; } virtual void Show2() { cout << "Show2" << endl; } virtual void Show3() { cout << "Show3" << endl; } }; // 模板方法 class ZooShowEx1: public ZooShow { protected: virtual bool Show0() { cout << "Show0 Ex1" << endl; if (!expired) { // 里氏替换原则 return true; } return false; } }; class ZooShowEx2: public ZooShow { protected: virtual void Show2() { cout << "Show2 E2" << endl; } }; class ZooShowEx3: public ZooShow { protected: virtual bool Show0() { cout << "Show0 Ex3" << endl; if (!expired) { return true; } return false; } virtual void Show2() { cout << "Show2 Ex3" << endl; } }; int main() { std::cout << "Template Method!" << std::endl; // 第一次迭代 ZooShow * zs1 = new ZooShowEx1; // 晚绑定 zs1->Show(); // 第二次迭代 ZooShow * zs2 = new ZooShowEx2; zs2->Show(); // 第三次迭代 ZooShow * zs3 = new ZooShowEx3; zs3->Show(); return 0; }
七、观察者模式
- 解决的问题:
- 稳定点:“一” 对 “多” 的依赖关系,“一” 变化,“多” 跟着变化。
- 变化点:“多” 增加、“多” 减少。
- 符合哪些设计原则:
- 面向接口编程。
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口。
- 用户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
- 减少系统中各部分的依赖关系,从而实现高内聚、低耦合的类型设计方案。
- 接口隔离:类与类的依赖建立在一个接口上。
- 容器存储接口。
- 依赖注入。
- 封装变化点:通过
Attach
和Detach
接口来封装变化点。
- 面向接口编程。
- 如何扩展:
- 继承实现接口。
- 调用
Attach
、Detach
。
- 代码结构:
- 实现观察接口。
- 多个观察者类实现。
- 客户类通过容器组合基类指针。
- 通过依赖注入增加或者删除观察对象。
// 背景:气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到三个不同的显示终端 // "一" 指的是数据中心,"多" 指的是不同的显示终端 using namespace std; // 观察接口 class IDisplay { public: virtual void Show(float temperature) = 0; virtual ~IDisplay() { } }; // 观察者类 class DisplayA: public IDisplay { public: virtual void Show(float temperature) { cout << "DisplayA show" << endl; } }; class DisplayB: public IDisplay { public: virtual void Show(float temperature) { cout << "DisplayB show" << endl; } }; class DisplayC: public IDisplay { virtual void Show(float temperature) { cout << "DisplayC show" << endl; } }; class DataCenter { public: // 依赖注入 void Attach(IDisplay * ob) { obs.push_back(ob); } // 依赖注入 void Detach(IDisplay * ob) { obs.remove(ob); } void notify() { float temperature = CalcTemperature(); for (auto ob : obs) { ob->Show(temperature); } } private: float