设计原则
依赖倒置原则(DIP)
高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖 于抽象(稳定)
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于 抽象(稳定)
开放封闭原则(OCP)
对扩展开放,对更改封闭
类模块应该是可扩展的,但是不可修改
模版方法(Template Method)
模式动机
一些功能在底层开发模块时不知具体实现,而需要在高层模块实现
模式定义
定义一个操作中的算法的骨架(稳定),而将一些步骤延迟 (变化)到子类中。Template Method使得子类可以不改变 (复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤
实现方法
由高层模块实现具体方法,其继承自包含该方法的基类。低层模块调用该基类upcast后的函数来执行
单例模式(Singleton Pattern)
模式动机
基于性能和软件正确性方面的考量,某些类只允许创建一次
模式定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点
实现方法
第一次调用创建新对象,当创建了该对象后再次调用则返回已创建的对象
具体例子(推荐的实现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Singleton{ private: Singleton(); Singleton(const Singleton& other); public: Singleton* instance; static Singleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mtx); if (instance == nullptr) instance = new Singleton(); } return instance; } };
|
工厂方法(Factory Method)
模式动机
在创建对象的时候不确定其具体类型,无法通过new来创建对象
模式定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟(目的:解耦, 手段:虚函数)到子类
实现方法
定义工厂类用于创建对象。创建一个工厂基类,不同的具体工厂类继承该基类。在低层模块创建对象时调用基类指针upcast之后的具体工厂的具体类创建方法。将不稳定隔离在上层,由上层传递具体工厂的对象给下层
具体例子
不好的实现
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MainForm : public Form { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar;
public: void Button1_Click(){ ISplitter * splitter = new BinarySplitter(); splitter->split(); } };
|
推荐的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class MainForm : public Form { SplitterFactory* factory; public: MainForm(SplitterFactory* factory){ this->factory=factory; }
void Button1_Click(){ ISplitter * splitter = factory->CreateSplitter(); splitter->split(); } };
class SplitterFactory{ public: virtual ISplitter* CreateSplitter()=0; virtual ~SplitterFactory(){} };
class BinarySplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new BinarySplitter(); } };
class TxtSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new TxtSplitter(); } };
|
观察者模式(Observer Pattern)
模式动机
建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应
模式定义
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新
实现方法
由订阅者实现具体的通知反应方法,其继承自通知基类。发布者在发送通知时调用该基类upcast后的函数来执行通知流程
具体例子
实现一个文件切分的进度条通知机制
不好的实现
在本实现中,由切分器实现具体的通知方法。存在的问题是切分器应该是较为稳定的模块,如果跟换了进度通知方法(例如控制台输出等)则需要修改split
方法
该实现违反了依赖倒置原则(高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖 于抽象(稳定))
由于切分器是较为稳定的模块(稳定),而UI界面才是与进度展示方式相绑定模块(不稳定)。该法将不稳定的通知实现绑定在了稳定的切分器模块,所以不是一个好的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class FileSplitter { string m_filePath; int m_fileNumber; ProgressBar* m_progressBar;
public: FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) : m_filePath(filePath), m_fileNumber(fileNumber), m_progressBar(progressBar){
} void split(){ for (int i = 0; i < m_fileNumber; i++){ float progressValue = m_fileNumber; progressValue = (i + 1) / progressValue; m_progressBar->setValue(progressValue); } }
class MainForm : public Form { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar;
public: void Button1_Click(){ string filePath = txtFilePath->getText(); int number = atoi(txtFileNumber->getText().c_str()); FileSplitter splitter(filePath, number, progressBar); splitter.split(); } };
|
推荐的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| class IProgress{ public: virtual void DoProgress(float value)=0; virtual ~IProgress(){} };
class FileSplitter { string m_filePath; int m_fileNumber; List<IProgress*> m_iprogressList; public: FileSplitter(const string& filePath, int fileNumber) : m_filePath(filePath), m_fileNumber(fileNumber){ }
void split(){ for (int i = 0; i < m_fileNumber; i++){ float progressValue = m_fileNumber; progressValue = (i + 1) / progressValue; onProgress(progressValue); } }
void addIProgress(IProgress* iprogress){ m_iprogressList.push_back(iprogress); }
void removeIProgress(IProgress* iprogress){ m_iprogressList.remove(iprogress); }
protected: virtual void onProgress(float value){ List<IProgress*>::iterator itor=m_iprogressList.begin(); while (itor != m_iprogressList.end() ) (*itor)->DoProgress(value); itor++; }
class MainForm : public Form, public IProgress { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar; public: void Button1_Click(){ string filePath = txtFilePath->getText(); int number = atoi(txtFileNumber->getText().c_str()); ConsoleNotifier cn; FileSplitter splitter(filePath, number); splitter.addIProgress(this); splitter.addIProgress(&cn); splitter.split(); splitter.removeIProgress(this); }
virtual void DoProgress(float value){ progressBar->setValue(value); } };
class ConsoleNotifier : public IProgress { public: virtual void DoProgress(float value){ cout << "."; } };
|
观察者模式 VS 发布订阅模式(Publish–Subscribe pattern)
发布订阅模式(架构模式)中,发布者并不知道订阅者的存在,只是把消息发送给broker。订阅者通过订阅相关的topic获取消息。如下图所示;而在观察者模式(设计模式)中,发布者会通知订阅者,只不过具体的通知操作由订阅者自己实现
特性 |
观察者模式 |
发布-订阅模式 |
耦合度 |
被观察者和观察者之间耦合度较高 |
发布者和订阅者之间完全解耦 |
通信方式 |
直接通信(被观察者调用观察者的方法) |
间接通信(通过消息代理) |
灵活性 |
适合固定的、一对多的依赖关系 |
适合动态的、多对多的消息传递 |
复杂性 |
实现简单 |
实现复杂,需要引入消息代理 |
适用场景 |
对象状态变化需要通知其他对象 |
需要高度解耦的分布式系统或事件驱动系统 |