常用设计模式讲解(史上最简短)

本文最后更新于:2023年1月1日 下午

这篇文章会简单粗暴地讲解一下常用设计模式,算是一个汇总,不会作详细地讲解

系统设计从设计原则开始,在过程中会自然而然发现需要加入模式的地方。所有的原则都不是必须遵守的,要考虑全局进行舍取,常常要折中。

所有的设计模式都是一种思想,即使在某种场合下没有办法实现常规的写法,但是用到它们的思想就可以了。尽可能保持设计的简单,只有真正需要模式时才使用,复杂度一般与灵活性正相关。

使用设计模式的目的:在不改变设计好的运行框架的前提下,需增加功能是只需要额外添加一个类,然后就可以在系统运行时增加功能

适配器模式

能够被适配的目标类一定有着可重写的接口,能够被适配的目标函数一定是虚函数

代码

1
2
3
4
5
6
7
8
class Adapter : public absClassA
{
absClassB* realInstance; //被适配
def func() override
{
//Todo:调用realInstance的一些方法
}
};

设计原则

尽量减少一个类中对于其它类的引用,以减少调用一个功能需要.出来的次数

装饰者模式

这个模式中使用继承是为了能够做到类型匹配,因为一个对象在被装饰之后还必须是原来的那个对象类型。不能因为装饰而改变自己的类型。一个化了妆的人还是一个人

能够被装饰的类一定有着可重写的接口,能够被装饰的函数一定是虚函数

代码

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
{
#if 0
//保护代理
//通过一些条件控制对realInstance的访问
if(满足条件)
realInstance->func();
#elif 0
//虚拟代理
if(realInstance != NULL)
realInstance->func();
else
//开启异步线程创建absClass实例
//或 执行默认操作
#else
//远程代理
realInstance->func(); //func通过网络和远程实例通信
#endif
}
};

对比

装饰者模式的目的是为对象加上行为,而代理模式的目的是控制对对象的访问

其他代理

  • 远程代理:代表特定远程实例
  • 防火墙代理:公司防火墙系统
  • 智能引用代理:引用计数
  • 缓存代理: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


常用设计模式讲解(史上最简短)
https://roudersky.com/posts/517ed801.html
作者
Rouder
发布于
2021年12月30日
许可协议