设计模式
大约 11 分钟
设计模式
类相互通信的对象之间的组织关系
设计模式术语
原则
- 先不采用设计模式, 在重构后选择设计模式
- 使用设计模式前需要寻找变化点
- 设计模式前提, 必须要有稳定点
绑定
- 将框架称为早, 将应用称为晚
- 早绑定即应用调用框架实现功能
- 晚绑定即框架调用应用实现功能
术语
复用
- 指编译时复用, 代码被复用即代码编译后的二进制信息不被更改, 不是编程语句不被更改
- 设计原则的目的之一是编译时复用, 将变化交给运行时
依赖
指编译时依赖, 当 A 依赖于 B, 即当编译时没有 B, A 无法编译通过
面向对象设计模式
面向对象的目的: 抵抗变化
依赖倒置原则
- 高层模块 (稳定) 不应该依赖于底层模块 (不稳定)
- 二者应该依赖于抽象 (稳定)
- 实现细节 (不稳定) 应该依赖于抽象 (稳定)
- eg. 工厂模式
开放封闭原则
- 对扩展开放, 对更改封闭
- 通过增加底层模块的方式实现新功能, 而不是修改高层模块
- eg. 模板模式
单一职责原则
- 一个类应该只有一个责任
- 因此一个类只能有一个变化方向
Liskov 替换原则
子类应该表达父类的所有方法, 不能空置
接口隔离原则
- 接口 (公有方法) 应当小而完备, 否则接口一旦修改, 客户程序也需要修改
- 一个接口类 (纯虚类), 应当只保留一系列等价地接口, 不能同时表达两种接口
- 桥模式
优先使用对象组合
- 继承时, 将导致类的耦合度增加
- 通过良好的接口, 可以避免继承必要
- eg. Decorator 模式
封装变化点
使用封装来创建对象之间的分界
针对接口编程
- 成员不使用具体的类, 而是使用接口 (ABC)
- 保存接口, 调用接口实现功能
- 对象组合中, 因保存接口指针, 而不是具体的类
组件协作模式
使用晚绑定, 实现框架与应用之间的松耦合
模板方法 Template Method
- 在框架中定义一个操作的算法 (稳定)
- 将算法中一些步骤的实现交给应用 (变化)
- 以此实现在复用算法的前提下, 改变内部的特定步骤
适用情况
- 必须有一个稳定的算法的骨架
- 骨架内部的步骤不稳定
实现
class Framework{
public:
// 在框架中定义一个操作的算法
void run(){
Step1();
...
Step2();
...
Step3();
}
protected:
// 算法中的固定步骤
void Step1(){...}
void Step3(){...}
// 算法中的变化步骤, 交给应用实现
virtual void Step2() = 0;
};
class Application : public Framework{
protected:
virtual void Step2(){...}
};
int main()
{
Framework* app = new Application;
app->run();
delete app;
return 0;
}
实例
- 对于窗体具有明确的启动流程, 但有部分启动细节可供修改
策略模式 Strategy
- 主类的接口固定 (稳定)
- 根据不同情况, 主类的接口实现方式不同 (不变)
- 为主类提供了一系列算法, 并且可以在运行时灵活切换
- 为未来的扩展提供了可能, 避免修改代码 (保证开放封闭原则)
适用情况
- 当代码中出现 switch / if-else 判断方法, 且可能性不限时, 极有可能是需要使用 策略模式 的标识
- 对于同一个操作, 在不同情况下, 有不同的解决方法, 且情况无限
- 对于同一个操作, 可以采用不同的方案
实现
class TaxStrategy{
public:
virtual void Calculate(Context& context) = 0;
virtual ~TaxStrategy();
};
class CNTaxStrategy : TaxStrategy{
public:
virtual void Calculate(Context& context){...}
};
class USTaxStrategy : TaxStrategy{
public:
virtual void Calculate(Context& context){...}
};
class SaleBox{
private:
unique_ptr<TaxStrategy> taxStrategy;
public:
// taxStrategy 的获取可使用工厂模式的方法
SaleBox(){
...
}
void CalculateTax(){
...
taxStrategy->Calculate(context);
}
};
实例
- 计算税率, 根据选择的国家切换税率计算方法
观察者模式 Obverser
- 主类在运行时会产生消息 (固定)
- 处理消息的类不同 (变化)
- 主类产生消息时, 希望所有消息依赖都能接收到消息并处理
- 将接收消息 (通知机制) 抽象为接口, 主类只需将消息通过接口传递给处理类
- 通知通过 list 传播, 观察者自行决定是否要观察, 而主类对于是否有观察不关系
实现
class ProgressObserver{
public:
// 将通知作为接口
virtual void Notify(float value) = 0;
~ProgressObserver();
};
// 使用一个列表管理多个观察者
class ProgressObserverList{
private:
// 使用 list, 满足删除与遍历功能
list<ProgressObserver*> _list;
public:
AttachObserver(ProgressObserver*);
DetachObserver(ProgressObserver*);
Notify(float value){
for(auto it : _list){
it->Notify(value);
}
}
}
class DoSomething{
...
ProgressObserverList _progressObserverList;
public:
void Progress(){
...
for(int i = 0; i < num; i++){
...
_progressObserverList.Notify((i + 1.0) / num);
}
}
}
实例
- 执行任务时, 向外部通知任务进度
- 观察者 A 将进度打印到屏幕, 观察者 B 绘制成进度条
单一职责模式
- 组件设计过程中, 如果职责设计不清晰, 将导致子类膨胀, 残生大量重复代码
- 可将职责理解为单一类型的接口
- 使用组合代替继承, 实现单职责的扩展 (Decorator) 与多职责的组合 (桥模式)
Decorator 模式
- 对于一类操作, 其职责单一且不变
- 但希望在其主要过程中添加部分额外扩展, 并且扩展可以自由组合
- 将这些扩展称为装饰器 Decorator
- 本质为将编译时装配变为运行时装配
- 动态地给一个对象增加职责, 并且比继承更加灵活
适用情况
- 对于操作 A 与 B, 扩展功能 X 与 Y
- 出现 XA, YA, XYA, XB, YB, XYB 等繁杂的复合操作
- 并且在这些组合类中频繁调用父类的函数
实现
class Stream{
public:
virtual void Read() = 0;
virtual void Write() = 0;
virtual ~Stream(){};
};
// 同一类操作
// 此类操作同为扩展, 但各个扩展之间相互排斥, 因此不可设为修饰器
class FileStream : public Stream{
public:
virtual void Read(){...};
virtual void Write(){...};
};
class MemoryStream : public Stream{
public:
virtual void Read(){...};
virtual void Write(){...};
};
// 不同的扩展修饰, 各个扩展可以相容
// 定义一个修饰器基类, 继承 Stream 的目的不是与父类组合, 只是为了与父类共享接口
class DecoratorStream : public Stream{
protected:
// 修饰器的核心关键, 继承被修饰类, 并且保存被修饰类
// 用组合代替继承, 继承中只能调用父类, 但组合中, 被组合的对象可以变化 (指针具有多态性)
Stream* _stream;
public:
DecoratorStream(Stream* stream) : _stream(stream)
{}
};
class CryptoStream : public Stream{
public:
CryptoStream(Stream* stream) : DecoratorStream(stream)
{}
virtual void Read(){
...
// 在修饰器中调用被修饰类实现修饰效果
// 代替原先调用父类的方法
_stream->Read();
...
};
virtual void Write(){...};
};
class BufferStream : public Stream{
public:
BufferStream(Stream* stream) : DecoratorStream(stream)
{}
virtual void Read(){...};
virtual void Write(){...};
};
int main(){
FileStream s1;
// 使用 CryptoStream 修饰 s1, 使其获得扩展
CryptoStream s2 = CryptoStream(&s1);
BufferStream s3 = BufferStream(&s1);
// 嵌套修饰, 从而使 s1 同时被两种修饰
BufferStream s4 = BufferStream(&s2);
return 0;
}
桥模式
- 主类有多个固定的, 相互独立职责 (Decorator 模式中, 可扩展职责只有一个, 可将职责理解为一组接口)
- 各个职责的具体实现形式不同
- 将一个类的职责分离为多个单职责, 最后用一个类组合所有职责
- 与 Decorator 模式同样利用了指针的多态性, 用组合代替继承, 并在运行时装配
- 其中的单个职责可以使用 Decorator 模式实现进一步扩展
适用情况
- 存在多个抽象维度且各个变化维度均需要扩展
- 一个主类同时继承了多个单一类型的接口
实现
class Display{
public:
virtual void Draw() = 0;
virtual ~Display();
};
class Ability{
public:
virtual void Fight() = 0;
virtual ~Ability();
};
class Unit{
protected:
Display* _display;
Ability* _ability;
public:
// 与 Decorator 相同, 在运行时装配
Unit(Display* display, Ability* ability):
_display(display), _ability(ability){
}
void Fight(Unit* obj){
...
bool isWin = _ability->Fight(obj);
...
}
void Draw(){
...
_display->Draw();
}
};
// 此部分的职责相互独立
class EnemyDisplay : public Display{
public:
virtual void Draw(){...};
virtual ~Display();
};
class PlayerDisplay : public Display{
public:
virtual void Draw(){...};
};
// 此部分职责可扩展, 可以像 Decorator 模式一样扩展
class DecoratorAbility : public Ability{
protected:
Ability* _ability;
public:
DecoratorAbility(Ability* ability) : _ability(ability)
{}
}
...
//
对象创建模式
在对象创建的过程中避开 new 导致的紧耦合
Factory Method 工厂方法
- 成员函数需要用到某个具体接口作为函数内的临时变量 (函数内依赖)
- 此具体接口具有多态性
- 成员函数中用到了具体的子类时, 无法通过一般的组合方式实现多态
- 如果每次将具体的子类传入成员函数 (new), 将导致传入的子类职责混乱
- 通过将工厂对象作为成员组合, 在需要子类时通过工厂创建
- 工厂的本质即一个创建工厂的接口
- 工厂的多态性从而保证了函数内依赖的多态性, 让主类决定实例化哪个类
- 缺点: 要求函数内依赖的创建方式必须一致, 因此在工厂方法中, 必须优先设计接口
适用情况
- 在成员函数中, 出现了 [接口指针] = new [具体实现], 导致了主类依赖与具体类
- 当出现以实现某种功能为职责的接口 (只完成某种具体功能) 时, 很可能需要工厂方法
实现
// 具体功能及接口
class IZip{
public:
virtual void Progress() = 0;
virtual ~IZip(){};
};
class NormalZip : public IZip{
public:
virtual void Progress(){...};
};
class CryptoZip : public IZip{
public:
virtual void Progress(){...};
};
// 为每个具体实现设计一个工厂
class IZipFactory{
public:
// 如果工厂不需要而外参数, 可定义为静态类
// 工厂函数返回产品的指针
virtual static IZip* CreateZip() = 0;
};
class NormalZipFactory : public IZipFactory{
public:
virtual static IZip* CreateZip(){
return new NormalZip();
};
};
class CryptoZipFactory : public IZipFactory{
public:
virtual static IZip* CreateZip(){
return new CryptoZip();
};
};
// 在具体实例中使用工厂
class ZipApp{
protected:
// 将工厂与类组合
IZipFactory* _factory;
public:
ZipApp(IZipFactory* factory): _factory(factory)
{}
void StartZip(){
...
// 有工厂创建函数内依赖
IZip* zip = _factory->CreateZip();
zip->Process(...);
...
// 明确职责, 工厂产生的产品由子类处理
delete zip;
}
}
Abstract Factory 抽象工厂 (Family Factory)
- 抽象工厂建立在工厂模式的基础上, 当一个类中的多个工厂间存在联系时适用. 如当使用了工厂 A1, 则必定会使用 A2; 若使用 B1 则并定会使用 B2. 即工厂间存在内部依赖.
- 因此可使用一个抽象工厂同时承担创建这一系列相关工厂的职责, 实现这一系列依赖的紧耦合
适用情况
实现
// 接口定义
class IDBDataReader{
...
}
class IDBCommand{
public:
virtual IDBDataReader CommandText(string) = 0;
};
class IDBConnectInfo{
public:
virtual string Info() = 0;
virtual string Warn() = 0;
};
// 具体实例定义 (MySql)
// 其中 SqlConnect 为一个由第三方库定义用于操作数据库的类
class SqlDataReader: public IDBDataReader{
...
}
class SqlDBCommand: public IDBCommand{
public:
SqlDBCommand(SqlConnect*);
virtual IDBDataReader Command(string){
...
};
};
class SqlConnectInfo: public IDBConnectInfo{
public:
SqlConnectInfo(SqlConnect*);
virtual string Info(){
...
};
virtual string Warn(){
...
};
};
// 具体实例定义 (ORACLE)
// 其中 OracleConnect 为一个由第三方库定义用于操作数据库的类
class ORACLEDataReader: public IDBDataReader{
...
}
class ORACLEDBCommand: public IDBCommand{
public:
ORACLEDBCommand(OracleConnect*){
...
}
virtual IDBDataReader Command(string){
...
};
};
class ORACLEConnectInfo: public IDBConnectInfo{
public:
ORACLEConnectInfo(ORACLEConnect*){
...
}
virtual string Info(){
...
};
virtual string Warn(){
...
};
};
// 抽象工厂接口定义
class IDBFactory{
public:
virtual IDBCommand CreateCommand() = 0;
virtual IDBConnectInfo CreateConnectInfo() = 0;
};
// 具体工厂定义
class SqlFactory : public IDBFactory{
public:
SqlConnect* connect;
// 注意, 可以通过向工厂的构造函数传入参数, 实现同一个工厂制造不同的产品 (一般工厂模式也可使用此方法)
SqlFactory(SqlConnect*){
...
}
// 在一个工厂中生产同一系列的多个产品, 同时这些产品公用一套原料
virtual IDBCommand CreateCommand(){
return new SqlCommand(connect);
}
virtual IDBConnectInfo CreateConnectInfo(){
return new SqlConnectInfo(connect);
}
}
class ORACLEFactory : public IDBFactory{
public:
ORACLEConnect* connect;
// 注意, 可以通过向工厂的构造函数传入参数, 实现同一个工厂制造不同的产品 (一般工厂模式也可使用此方法)
ORACLEFactory(ORACLEConnect*){
...
}
virtual IDBCommand CreateCommand(){
return new ORACLECommand(connect);
}
virtual IDBConnectInfo CreateConnectInfo(){
return new ORACLEConnectInfo(connect);
}
}