本文最后更新于:2023年1月1日 下午
这篇文章会简单粗暴地讲解一下常用设计模式,算是一个汇总,不会作详细地讲解
系统设计从设计原则开始,在过程中会自然而然发现需要加入模式的地方。所有的原则都不是必须遵守的,要考虑全局进行舍取,常常要折中。
所有的设计模式都是一种思想,即使在某种场合下没有办法实现常规的写法,但是用到它们的思想就可以了。尽可能保持设计的简单,只有真正需要模式时才使用,复杂度一般与灵活性正相关。
使用设计模式的目的:在不改变设计好的运行框架的前提下,需增加功能是只需要额外添加一个类,然后就可以在系统运行时增加功能
适配器模式
能够被适配的目标类一定有着可重写的接口,能够被适配的目标函数一定是虚函数
代码
1 2 3 4 5 6 7 8
| class Adapter : public absClassA { absClassB* realInstance; def func() override { } };
|
设计原则
尽量减少一个类中对于其它类的引用,以减少调用一个功能需要.出来的次数
装饰者模式
这个模式中使用继承是为了能够做到类型匹配,因为一个对象在被装饰之后还必须是原来的那个对象类型。不能因为装饰而改变自己的类型。一个化了妆的人还是一个人
能够被装饰的类一定有着可重写的接口,能够被装饰的函数一定是虚函数
代码
1 2 3 4 5 6 7 8 9
| class Decorator : absClass { absClass* realInstance; //被装饰者 def func() override { realInstance->func(); //Todo:do more something } };
|
设计原则
对扩展开放,对修改关闭
代理模式
代理(proxy):代表特定实例
能够被代理的类一定有着可重写的接口,能够被代理化的函数一定是虚函数
创建代理通常会使用工厂方法
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Proxy : absClass { absClass* realInstance; void func() override {
//保护代理 //通过一些条件控制对realInstance的访问 if(满足条件) realInstance->func();
//虚拟代理 if(realInstance != NULL) realInstance->func(); else //开启异步线程创建absClass实例 //或 执行默认操作
//远程代理 realInstance->func(); //func通过网络和远程实例通信
} };
|
对比
装饰者模式的目的是为对象加上行为,而代理模式的目的是控制对对象的访问
其他代理
- 远程代理:代表特定远程实例
- 防火墙代理:公司防火墙系统
- 智能引用代理:引用计数
- 缓存代理:Web服务器
- 同步代理:JavaSpace
- 复杂隐藏代理(外观理解):字典
- 写入时复制代理:Java5的CopyOnWriteArrayList
观察者模式
观察感兴趣的事物 或者 事情
代码
1 2 3 4
| class Subject //可以被包含 { absClass*[] observers; //核心数据结构 }
|
设计原则
对象之间保持松耦合
迭代器与组合模式
组合模式代码
1 2 3 4
| class xxx:absClass { absClass*[] a; }
|
迭代器模式代码
1 2 3 4 5
| class Iterator { def hasNext() = 0; def next() = 0; }
|
组合迭代器模式代码
1 2 3 4 5 6
| class xxx:Iterator { Iterator*[] it; def hasNext() override {...} def next() override {...} }
|
设计原则
每个类保持单一的责任
策略模式
多用接口
代码
1 2 3 4 5 6 7
| class xxx { absClassA* a absClassB* b absClassC* c ... }
|
设计原则
- 多用组合,少用继承
- 针对接口编程,不针对具体实现编程
- 将变和不变的内容分开
状态模式
基于有限状态机;允许对象在内部状态改变时改变它的行为,对象好像在运行过程中修改了它的类定义
- 让一个类具有状态机功能的方法:
- 让类中拥有一个State成员
- 让类中拥有一个StateMachine成员
- 较复杂
- state <--> stateMachine <--> object
代码
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
| class State { def Enter() = 0; def Exit() = 0;
def DoSomethingA() = 0; ...
}
class StateMachine { State* curState; def changeState(State* newState) { curState->Exit(); curState = newState; curState->Enter(); }
def DoSomethingA() { curState->DoSomethingA(); } ... }
|
对比
策略模式和状态模式本质上是相同的,只是状态模式在执行逻辑时,状态可以自动发生改变
适用条件
- 实体的行为基于一些内在状态
- 状态可以被严格的分割为相对较少的不相干项目
- 实体响应一系列输入或事件
- 状态的跳转呈现出复杂的图结构
应用场景
- 玩家输入
- 导航菜单界面
- 分析文字
- 网络协议
- 其他异步行为
并发状态机
降低了状态的数量
一个对象拥有两个状态机,分别负责m种A类状态、n种B类状态,实现A类状态和B类状态的组合
分层状态机
实现了代码重用
用继承来实现:子类不处理的输入交给来父类处理
用栈来实现:每一个元素都是它下一个元素的子状态,接收到输入时,从栈顶开始向下查找可以处理输入的那个状态来处理该输入
下推状态机
实现了状态回退功能
状态机使用一个栈来记录历史状态,栈顶代表当前状态
Meyers单例模式
C++11标准之后的最佳选择
全局唯一、有全局访问点,但延迟构建不是必须的
1 2 3 4 5 6 7 8 9
| class Singleton { public: static Singleton* getInstance() { static Singleton s; return &s; } }
|
- 缺点
- 不可控制创建时间
- 不可控制析构顺序
- getInstance的开销可能很大
一般类的构造和析构函数可以不做任何事情,定义额外的startUp和shutDown函数
- 管理多个单例的启动和终止的方法
- 手动控制启动和终止
- 把各个单例类登记在一个列表中,逐一启动
- 建立各个单例类之间的依赖关系图(dependency
graph),自动计算出最优启动顺序和终止顺序
命令模式
将发出请求的对象和执行请求的对象进行解耦
命令模式的一个强大功能就是撤销
1 2 3 4 5 6 7 8 9 10 11
| class Command { void execute() = 0; void undo() = 0; }
class Remote { map<int, Command*> commands; statck<Command*> historyCommands; }
|
应用场景
模板方法模式
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Template { def mainProcess() { funcA(); funcB(); if(funcC()) funcD(); ... } def funcA() = 0; def funcB() = 0; def funcC() = 0; def funcD() = 0; }
|
设计原则
低层组件不可以调用高层组件,但是高层组件可以调用低层组件
工厂模式
抽象类的具体类比较多、需要分类或创建过程比较复杂时,可以用工厂模式将创建它们的函数封装起来
简单工厂模式(Simple Factory)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class AbsProduct {
}
class Product1 : AbsProduct {
}
class Product2 : AbsProduct {
}
AbsProduct* create(string name) { if(...) return new Product1(); else if(...) return new Product2(); ... }
|
工厂方法模式(Factory Method)
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
| class AbsProduct {
}
class Product1 : AbsProduct {
}
class Product2 : AbsProduct {
}
...
class AbsFactory { public: virtual AbsProduct createProduct() = 0; }
class Factory1 : AbsFactory { public: override AbsProduct createProduct() { ... return new Product1(); } }
class Factory2 : AbsFactory { public: override AbsProduct createProduct() { ... return new Product2(); } }
...
|
抽象工厂模式(Abstract
Factory)
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
| class AbsProductA {
}
class ProductA1 : AbsProductA {
}
class ProductA2 : AbsProductA {
}
...
class AbsProductB {
}
class ProductB1 : AbsProductB {
}
class ProductB2 : AbsProductB {
}
...
class AbsFactory { public: virtual AbsProductA createProductA() = 0; virtual AbsProductB createProductB() = 0; }
class Factory1 : AbsFactory { public: override AbsProductA createProductA() { ... return new ProductA1(); }
override AbsProductB createProductB() { ... return new ProductB1(); } }
class Factory2 : AbsFactory { public: override AbsProductA createProductA() { ... return new ProductA2(); }
override AbsProductB createProductB() { ... return new ProductB2(); } }
...
|
设计原则
依赖倒置原则:要依赖抽象类,不要依赖具体类
MVC
复合模式:结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题
MVC的本质就是将数据、显示、逻辑分开,一切以实现这个目标为目的的设计都可以说是MVC,以下是其中一种设计:
- controller响应view的调用来控制view和model
- model利用观察者通知controller和view
- view利用策略模式接上controller
- view利用组合模式管理所有组件。由于现在的GUI系统都比较先进,所以一般不用再使用组合模式进行管理了
view和controller之间的关系可以是1对1、1对n、n对1