设计模式高频总结二
解释策略模式和状态模式之间的区别,以及在什么情况下使用它们
策略模式和状态模式的概念
策略模式(Strategy Pattern)
定义:策略模式是一种行为型设计模式,它定义了一系列算法,并将每种算法封装起来,使它们可以互相替换,从而使得算法的变化不会影响使用算法的客户端。
核心思想:
- 将具体的行为实现(算法)抽象化,客户端可以在运行时动态选择行为。
- 强调 行为的可替换性。
适用场景:
- 系统中有多种算法可以实现相同的功能。
- 希望客户端在运行时选择不同的算法。
- 避免在上下文类中写多个
if-else
或switch
语句。
状态模式(State Pattern)
定义:状态模式是一种行为型设计模式,它允许一个对象在其内部状态发生改变时,改变其行为。看起来就像是对象的类发生了变化一样。
核心思想:
- 将状态与行为绑定,每种状态封装不同的行为。
- 强调 对象行为的变化,而非行为的选择。
适用场景:
- 对象的行为依赖于其状态,并且可以根据状态改变行为。
- 需要避免大量的
if-else
或switch
判断逻辑来管理状态切换。 - 状态的切换逻辑复杂且需要可扩展性。
策略模式 vs 状态模式
特性 | 策略模式 | 状态模式 |
---|---|---|
目的 | 让算法(行为)可以互相替换 | 根据对象的内部状态动态改变其行为 |
关注点 | 解决多个算法的选择问题 | 解决对象在不同状态下的行为变化问题 |
模式结构 | 通常由上下文类和一组策略类组成 | 通常由上下文类、多个状态类及状态转移逻辑组成 |
客户端控制 | 客户端可以动态选择策略 | 状态的变化由上下文或状态类自行控制 |
可替换性 | 策略可以互相替换,不会影响上下文 | 状态之间通常不可替换,强调行为随状态变化 |
扩展性 | 添加新策略很方便 | 添加新状态需要在上下文类或状态类中添加逻辑 |
实现对比
1. 策略模式示例
场景:实现一个支付系统,支持不同的支付方式(如信用卡、支付宝、微信支付)。
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
#include <iostream>
#include <memory>
// 策略接口
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() {}
};
// 具体策略:信用卡支付
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " using Credit Card." << std::endl;
}
};
// 具体策略:支付宝支付
class AlipayPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " using Alipay." << std::endl;
}
};
// 具体策略:微信支付
class WeChatPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " using WeChat." << std::endl;
}
};
// 上下文类
class PaymentContext {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setStrategy(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void executePayment(int amount) {
if (strategy) {
strategy->pay(amount);
} else {
std::cout << "No payment strategy set!" << std::endl;
}
}
};
int main() {
PaymentContext context;
context.setStrategy(std::make_unique<CreditCardPayment>());
context.executePayment(100); // 输出:Paid 100 using Credit Card.
context.setStrategy(std::make_unique<AlipayPayment>());
context.executePayment(200); // 输出:Paid 200 using Alipay.
return 0;
}
分析:
- 支付方式(算法)可以随时切换。
- 每种支付方式是独立的,互不依赖,扩展新支付方式很简单。
2. 状态模式示例
场景:实现一个简单的文档编辑系统,文档有三种状态(草稿、审核中、发布),每种状态下行为不同。
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
#include <iostream>
#include <memory>
// 状态接口
class State {
public:
virtual void handleRequest() = 0;
virtual ~State() {}
};
// 具体状态:草稿状态
class DraftState : public State {
public:
void handleRequest() override {
std::cout << "Document is in Draft state." << std::endl;
}
};
// 具体状态:审核状态
class ReviewState : public State {
public:
void handleRequest() override {
std::cout << "Document is in Review state." << std::endl;
}
};
// 具体状态:发布状态
class PublishedState : public State {
public:
void handleRequest() override {
std::cout << "Document is Published." << std::endl;
}
};
// 上下文类
class Document {
std::unique_ptr<State> state;
public:
void setState(std::unique_ptr<State> s) {
state = std::move(s);
}
void process() {
if (state) {
state->handleRequest();
} else {
std::cout << "No state set!" << std::endl;
}
}
};
int main() {
Document doc;
doc.setState(std::make_unique<DraftState>());
doc.process(); // 输出:Document is in Draft state.
doc.setState(std::make_unique<ReviewState>());
doc.process(); // 输出:Document is in Review state.
doc.setState(std::make_unique<PublishedState>());
doc.process(); // 输出:Document is Published.
return 0;
}
分析:
- 每种状态都有独立的行为逻辑。
- 状态切换由上下文类(
Document
)控制。
使用策略模式的场景
- 系统中有多个算法需要动态选择。
- 客户端需要透明地使用不同算法。
- 需要避免使用大量的
if-else
或switch-case
。
例如:
- 支付系统支持不同支付方式。
- 不同的排序算法(快速排序、归并排序、冒泡排序)。
使用状态模式的场景
- 对象需要根据内部状态动态改变行为。
- 状态切换复杂且有逻辑关系。
- 需要避免
if-else
来管理状态。
例如:
- 文档编辑系统中,不同状态对应不同操作(草稿、审核、发布)。
- TCP 连接的不同状态(连接中、已连接、断开连接)。
总结
- 策略模式:适用于算法或行为可以互相替换且独立的场景。
- 状态模式:适用于对象行为依赖于内部状态变化的场景。
两者的关键区别在于:
- 策略模式强调算法替换。
- 状态模式强调对象状态驱动的行为变化。
什么是迭代器模式和组合模式?它们可以一起使用吗?
迭代器模式和组合模式的概念
迭代器模式(Iterator Pattern)
定义: 迭代器模式是一种行为型设计模式,它提供了一种方法顺序访问聚合对象中的各个元素,而无需暴露其内部实现。
核心思想:
- 将遍历行为与聚合对象分离。
- 提供一个统一的接口,让不同的聚合结构都可以被遍历。
适用场景:
- 需要遍历复杂的数据结构,但不希望暴露数据结构的内部细节。
- 支持多种遍历方式(如前序、中序、后序遍历)。
组合模式(Composite Pattern)
定义: 组合模式是一种结构型设计模式,它将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端可以以一致的方式处理单个对象和对象组合。
核心思想:
- 将叶子节点和容器节点统一看作是组件。
- 允许客户端忽略组合对象与单个对象的区别,统一处理它们。
适用场景:
- 希望客户端可以透明地操作单个对象和组合对象。
- 对象的结构呈现树形关系,例如文件系统、公司组织架构。
迭代器模式和组合模式的区别
特性 | 迭代器模式 | 组合模式 |
---|---|---|
目的 | 顺序访问聚合对象中的元素,不暴露其内部结构。 | 以树形结构表示对象的部分-整体关系,统一处理单个对象和组合对象。 |
关注点 | 数据结构的遍历行为 | 结构化组织和管理对象的层次关系 |
结构 | 通常包括一个聚合类和一个迭代器类 | 包括组件接口、叶子类和组合类 |
迭代器模式和组合模式可以一起使用吗?
是的,迭代器模式和组合模式可以一起使用。 组合模式常常用于构建树形结构,而迭代器模式可以用于遍历该树形结构。例如,在文件系统中:
- 组合模式用于表示目录和文件的树形结构。
- 迭代器模式用于遍历目录及其子目录中的所有文件。
代码示例
以下是一个结合迭代器模式和组合模式的示例:文件系统
组合模式
场景:文件系统包含两种类型的节点:
- 文件(叶子节点)。
- 目录(组合节点),可以包含文件和子目录。
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
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 组件基类
class FileSystemNode {
public:
virtual void print() const = 0; // 打印节点信息
virtual ~FileSystemNode() {}
};
// 叶子节点:文件
class File : public FileSystemNode {
std::string name;
public:
File(const std::string& name) : name(name) {}
void print() const override {
std::cout << "File: " << name << std::endl;
}
};
// 组合节点:目录
class Directory : public FileSystemNode {
std::string name;
std::vector<std::shared_ptr<FileSystemNode>> children;
public:
Directory(const std::string& name) : name(name) {}
void add(const std::shared_ptr<FileSystemNode>& child) {
children.push_back(child);
}
void print() const override {
std::cout << "Directory: " << name << std::endl;
for (const auto& child : children) {
child->print();
}
}
const std::vector<std::shared_ptr<FileSystemNode>>& getChildren() const {
return children;
}
};
迭代器模式
场景:遍历文件系统的所有节点(包括文件和目录)。
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
#include <stack>
// 文件系统迭代器
class FileSystemIterator {
std::stack<std::shared_ptr<FileSystemNode>> nodes;
public:
explicit FileSystemIterator(const std::shared_ptr<FileSystemNode>& root) {
nodes.push(root);
}
bool hasNext() const {
return !nodes.empty();
}
std::shared_ptr<FileSystemNode> next() {
if (!hasNext()) {
throw std::runtime_error("No more elements.");
}
auto current = nodes.top();
nodes.pop();
// 如果当前节点是目录,将其子节点入栈
if (auto dir = std::dynamic_pointer_cast<Directory>(current)) {
const auto& children = dir->getChildren();
for (auto it = children.rbegin(); it != children.rend(); ++it) {
nodes.push(*it);
}
}
return current;
}
};
客户端代码
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
int main() {
// 创建文件和目录
auto root = std::make_shared<Directory>("root");
auto dir1 = std::make_shared<Directory>("dir1");
auto dir2 = std::make_shared<Directory>("dir2");
auto file1 = std::make_shared<File>("file1.txt");
auto file2 = std::make_shared<File>("file2.txt");
auto file3 = std::make_shared<File>("file3.txt");
// 构建文件系统结构
root->add(file1);
root->add(dir1);
dir1->add(file2);
dir1->add(dir2);
dir2->add(file3);
// 打印整个结构
std::cout << "File System Structure:" << std::endl;
root->print();
// 使用迭代器遍历文件系统
std::cout << "\nIterating over File System:" << std::endl;
FileSystemIterator iterator(root);
while (iterator.hasNext()) {
auto node = iterator.next();
node->print();
}
return 0;
}
输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
File System Structure:
Directory: root
File: file1.txt
Directory: dir1
File: file2.txt
Directory: dir2
File: file3.txt
Iterating over File System:
Directory: root
File: file1.txt
Directory: dir1
File: file2.txt
Directory: dir2
File: file3.txt
分析
- 组合模式
- 用于表示文件和目录的树形层次结构。
- 客户端可以透明地处理单个文件或目录。
- 迭代器模式
- 用于遍历文件系统的所有节点(包括文件和目录)。
- 通过封装遍历逻辑,隐藏文件系统的内部实现。
- 结合使用
- 组合模式提供了灵活的结构化表示。
- 迭代器模式为组合结构提供了统一的遍历接口。
总结
- 组合模式:处理层次结构,统一叶子和组合对象的接口。
- 迭代器模式:提供统一的接口,遍历聚合对象的元素。
两者结合使用,可以有效解决复杂树形结构的表示和遍历问题,是文件系统、用户界面等场景中常见的模式组合。
解释桥接模式和适配器模式之间的区别,并说明在哪种情况下选择使用桥接或适配器
桥接模式和适配器模式的概念
桥接模式(Bridge Pattern)
定义: 桥接模式是一种结构型设计模式,它将 抽象部分 和 实现部分 分离,使它们可以独立变化。通过这种方式,可以在不修改抽象和实现的情况下扩展系统。
核心思想:
- 抽象和实现分离,形成两个独立的变化维度。
- 通过组合代替继承,减少类的复杂度。
适用场景:
- 一个类需要多个维度的变化(如抽象维度和实现维度)。
- 不希望因为某个维度的变化导致类的数量爆炸。
- 需要动态切换实现。
适配器模式(Adapter Pattern)
定义: 适配器模式是一种结构型设计模式,它将一个类的接口转换为客户端期望的另一个接口,使得原本由于接口不兼容而无法一起工作的类能够协同工作。
核心思想:
- 解决接口不兼容问题,提供适配层。
- 让原本不兼容的类可以与客户端协作。
适用场景:
- 系统需要使用已有类,但这些类的接口与当前需求不兼容。
- 使用第三方库或遗留代码,需要适配接口。
桥接模式和适配器模式的区别
特性 | 桥接模式 | 适配器模式 |
---|---|---|
目的 | 将抽象部分与实现部分分离,使它们可以独立扩展。 | 将已有类的接口转换为客户端期望的接口。 |
关系 | 抽象部分和实现部分通过组合建立关联。 | 客户端和适配器通过适配层建立关联。 |
应用场景 | 需要在多个维度上扩展类的功能。 | 接口不兼容但需要协同工作。 |
设计动机 | 减少类的数量,避免继承的复杂性。 | 解决现有代码或库的接口不兼容问题。 |
实现方式 | 抽象部分和实现部分各自定义接口,独立变化。 | 在客户端和已有类之间增加一个适配层。 |
如何选择桥接模式或适配器模式
- 使用桥接模式的场景:
- 系统需要在多个维度上扩展功能,例如形状和颜色的组合。
- 抽象部分和实现部分是相对独立的,可以单独扩展。
- 希望减少类的数量,避免使用继承导致的类爆炸。
- 使用适配器模式的场景:
- 需要复用现有类,但其接口与目标接口不兼容。
- 集成第三方库或遗留代码,需要调整接口。
- 解决运行时接口转换需求。
代码示例
1. 桥接模式
场景:实现一个图形系统,图形有多种形状(如圆形、矩形)和不同的渲染方式(如矢量渲染和光栅渲染)。
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
#include <iostream>
#include <memory>
// 实现接口
class Renderer {
public:
virtual void renderCircle(float radius) = 0;
virtual ~Renderer() {}
};
// 矢量渲染实现
class VectorRenderer : public Renderer {
public:
void renderCircle(float radius) override {
std::cout << "Drawing a circle with radius " << radius << " using Vector rendering." << std::endl;
}
};
// 光栅渲染实现
class RasterRenderer : public Renderer {
public:
void renderCircle(float radius) override {
std::cout << "Drawing a circle with radius " << radius << " using Raster rendering." << std::endl;
}
};
// 抽象部分
class Shape {
protected:
std::shared_ptr<Renderer> renderer;
public:
Shape(const std::shared_ptr<Renderer>& renderer) : renderer(renderer) {}
virtual void draw() = 0;
virtual ~Shape() {}
};
// 具体抽象部分:圆形
class Circle : public Shape {
float radius;
public:
Circle(const std::shared_ptr<Renderer>& renderer, float radius)
: Shape(renderer), radius(radius) {}
void draw() override {
renderer->renderCircle(radius);
}
};
int main() {
auto vectorRenderer = std::make_shared<VectorRenderer>();
auto rasterRenderer = std::make_shared<RasterRenderer>();
Circle circle1(vectorRenderer, 5.0f);
circle1.draw(); // 使用矢量渲染绘制圆形
Circle circle2(rasterRenderer, 10.0f);
circle2.draw(); // 使用光栅渲染绘制圆形
return 0;
}
分析:
- 图形的 抽象部分 是
Shape
(如Circle
)。 - 渲染方式的 实现部分 是
Renderer
(如VectorRenderer
和RasterRenderer
)。 - 抽象和实现可以独立扩展,例如新增形状或新增渲染方式。
2. 适配器模式
场景:客户端需要统一的绘图接口,但现有的绘图工具接口不兼容。
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
#include <iostream>
#include <memory>
// 目标接口
class DrawingAPI {
public:
virtual void drawCircle(float x, float y, float radius) = 0;
virtual ~DrawingAPI() {}
};
// 已有的绘图工具 A
class DrawingToolA {
public:
void draw(float x, float y, float radius) {
std::cout << "Tool A: Drawing a circle at (" << x << ", " << y
<< ") with radius " << radius << std::endl;
}
};
// 已有的绘图工具 B
class DrawingToolB {
public:
void render(float x, float y, float radius) {
std::cout << "Tool B: Rendering a circle at (" << x << ", " << y
<< ") with radius " << radius << std::endl;
}
};
// 适配器:将 Tool A 适配为目标接口
class ToolAAdapter : public DrawingAPI {
DrawingToolA toolA;
public:
void drawCircle(float x, float y, float radius) override {
toolA.draw(x, y, radius);
}
};
// 适配器:将 Tool B 适配为目标接口
class ToolBAdapter : public DrawingAPI {
DrawingToolB toolB;
public:
void drawCircle(float x, float y, float radius) override {
toolB.render(x, y, radius);
}
};
// 客户端
class Shape {
std::shared_ptr<DrawingAPI> api;
public:
Shape(const std::shared_ptr<DrawingAPI>& api) : api(api) {}
void draw(float x, float y, float radius) {
api->drawCircle(x, y, radius);
}
};
int main() {
auto toolAAdapter = std::make_shared<ToolAAdapter>();
auto toolBAdapter = std::make_shared<ToolBAdapter>();
Shape shapeA(toolAAdapter);
shapeA.draw(5, 5, 10); // 使用 Tool A 绘制
Shape shapeB(toolBAdapter);
shapeB.draw(10, 10, 20); // 使用 Tool B 绘制
return 0;
}
分析:
ToolAAdapter
和ToolBAdapter
是适配层,将已有工具的接口转换为统一的目标接口。- 客户端可以透明地使用适配器与不同的绘图工具协作。
总结
- 桥接模式:解决抽象和实现的分离问题,适用于多维度变化的场景。
- 示例:形状和渲染方式的组合。
- 适配器模式:解决接口不兼容问题,适用于整合已有类的场景。
- 示例:统一不同绘图工具的接口。
选择模式时,关注问题的核心是扩展性(桥接)还是兼容性(适配器)。
介绍责任链模式和命令模式的概念,并说明它们如何解耦发送者和接收者
责任链模式和命令模式的概念
责任链模式(Chain of Responsibility Pattern)
定义: 责任链模式是一种行为型设计模式,它将请求沿着一条处理链进行传递,直到链上的某个对象处理该请求为止。通过这种方式,可以让多个对象都有机会处理请求,从而实现请求的解耦。
核心思想:
- 将请求的发送者和处理者解耦。
- 让多个处理者有机会处理请求,但请求处理的具体逻辑对发送者是透明的。
适用场景:
- 多个对象可以处理同一个请求,具体处理者在运行时确定。
- 请求处理者之间需要形成一种链式传递关系。
- 希望避免请求的发送者和接收者之间的强耦合。
命令模式(Command Pattern)
定义: 命令模式是一种行为型设计模式,它将请求封装为一个命令对象,并将其传递给调用方,从而将请求的发送者和具体的执行者解耦。
核心思想:
- 将请求封装为一个独立的对象。
- 通过命令对象将请求的发送者和接收者分离。
适用场景:
- 需要对请求进行排队、记录、撤销或重做操作。
- 请求发送者和接收者需要完全解耦。
- 系统需要支持参数化的请求或延迟执行请求。
责任链模式和命令模式的解耦方式
特性 | 责任链模式 | 命令模式 |
---|---|---|
解耦方式 | 请求沿着责任链传递,发送者和处理者之间无直接联系。 | 请求被封装为命令对象,发送者只需调用命令接口,无需知道接收者。 |
请求处理机制 | 请求的处理顺序由责任链中的对象决定。 | 请求的执行由命令对象中指定的接收者完成。 |
适用重点 | 动态决定谁来处理请求,强调请求的分发机制。 | 动态绑定请求和处理者,强调请求的封装和执行。 |
实现示例
1. 责任链模式
场景:一个简单的日志系统,日志请求可以按级别传递到不同的处理者(如控制台日志、文件日志、邮件日志)。
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
76
77
78
79
80
#include <iostream>
#include <memory>
#include <string>
// 抽象处理者
class Logger {
protected:
std::shared_ptr<Logger> nextLogger;
public:
void setNextLogger(const std::shared_ptr<Logger>& next) {
nextLogger = next;
}
void logMessage(int level, const std::string& message) {
if (canHandle(level)) {
write(message);
} else if (nextLogger) {
nextLogger->logMessage(level, message); // 传递给下一个处理者
}
}
virtual ~Logger() {}
protected:
virtual bool canHandle(int level) = 0;
virtual void write(const std::string& message) = 0;
};
// 具体处理者:控制台日志
class ConsoleLogger : public Logger {
protected:
bool canHandle(int level) override {
return level <= 1; // 处理低优先级日志
}
void write(const std::string& message) override {
std::cout << "Console Logger: " << message << std::endl;
}
};
// 具体处理者:文件日志
class FileLogger : public Logger {
protected:
bool canHandle(int level) override {
return level == 2; // 处理中等优先级日志
}
void write(const std::string& message) override {
std::cout << "File Logger: " << message << std::endl;
}
};
// 具体处理者:邮件日志
class EmailLogger : public Logger {
protected:
bool canHandle(int level) override {
return level >= 3; // 处理高优先级日志
}
void write(const std::string& message) override {
std::cout << "Email Logger: " << message << std::endl;
}
};
int main() {
auto consoleLogger = std::make_shared<ConsoleLogger>();
auto fileLogger = std::make_shared<FileLogger>();
auto emailLogger = std::make_shared<EmailLogger>();
// 构建责任链
consoleLogger->setNextLogger(fileLogger);
fileLogger->setNextLogger(emailLogger);
// 发送日志请求
consoleLogger->logMessage(1, "This is a low-priority message.");
consoleLogger->logMessage(2, "This is a medium-priority message.");
consoleLogger->logMessage(3, "This is a high-priority message.");
return 0;
}
输出:
1
2
3
Console Logger: This is a low-priority message.
File Logger: This is a medium-priority message.
Email Logger: This is a high-priority message.
2. 命令模式
场景:一个简单的遥控器系统,可以发出打开和关闭灯光的命令。
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
76
77
78
79
80
#include <iostream>
#include <memory>
// 命令接口
class Command {
public:
virtual void execute() = 0;
virtual ~Command() {}
};
// 接收者
class Light {
public:
void turnOn() {
std::cout << "Light is ON" << std::endl;
}
void turnOff() {
std::cout << "Light is OFF" << std::endl;
}
};
// 具体命令:打开灯光
class LightOnCommand : public Command {
Light& light;
public:
explicit LightOnCommand(Light& light) : light(light) {}
void execute() override {
light.turnOn();
}
};
// 具体命令:关闭灯光
class LightOffCommand : public Command {
Light& light;
public:
explicit LightOffCommand(Light& light) : light(light) {}
void execute() override {
light.turnOff();
}
};
// 调用者
class RemoteControl {
std::shared_ptr<Command> command;
public:
void setCommand(const std::shared_ptr<Command>& cmd) {
command = cmd;
}
void pressButton() {
if (command) {
command->execute();
}
}
};
int main() {
Light light;
auto lightOn = std::make_shared<LightOnCommand>(light);
auto lightOff = std::make_shared<LightOffCommand>(light);
RemoteControl remote;
// 打开灯光
remote.setCommand(lightOn);
remote.pressButton(); // 输出:Light is ON
// 关闭灯光
remote.setCommand(lightOff);
remote.pressButton(); // 输出:Light is OFF
return 0;
}
分析:
- 命令对象(
LightOnCommand
和LightOffCommand
)将请求与接收者(Light
)解耦。 - 调用者(
RemoteControl
)只需与命令对象交互,而无需关心具体接收者的实现。
责任链模式和命令模式的对比
特性 | 责任链模式 | 命令模式 |
---|---|---|
解耦程度 | 发送者和多个处理者解耦。 | 发送者和单个接收者解耦。 |
处理机制 | 请求沿责任链传递,可能有多个处理者处理请求。 | 请求被封装为命令对象,由一个接收者执行。 |
适用场景 | 请求的处理者动态决定,且可能有多个处理者参与处理。 | 请求需要封装、排队或延迟执行。 |
总结
- 责任链模式:用于处理链式请求的场景,每个处理者可以选择处理请求或将其传递给下一个处理者。
- 命令模式:用于请求封装、参数化执行或记录请求的场景。
选择哪种模式取决于系统需求:
- 如果请求可能由多个处理者动态处理,选择 责任链模式。
- 如果需要将请求封装为对象以便于扩展、记录或延迟执行,选择 命令模式。
单例模式实现区别
单例模式的实现方式及其区别
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。 不同实现方式的主要区别在于线程安全性、懒加载、以及实现复杂度。
常见单例模式实现
1. 饿汉式单例(Eager Initialization)
特点:
- 在类加载时就创建实例。
- 线程安全(因为静态实例在类加载时初始化)。
- 不支持懒加载(即:无论是否使用,实例都会创建)。
实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
class Singleton {
private:
static Singleton instance; // 静态实例
Singleton() {} // 私有构造函数
public:
static Singleton& getInstance() {
return instance;
}
};
// 初始化静态实例
Singleton Singleton::instance;
int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
std::cout << (&s1 == &s2) << std::endl; // 输出 1,表示是同一个实例
return 0;
}
优点:
- 实现简单。
- 线程安全。
缺点:
- 无法实现懒加载(资源可能被浪费)。
2. 懒汉式单例(Lazy Initialization)
特点:
- 实例在第一次访问时创建。
- 默认情况下不是线程安全的,需要额外的同步机制。
实现代码:
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
#include <iostream>
class Singleton {
private:
static Singleton* instance; // 静态指针
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton(); // 第一次访问时创建实例
}
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
std::cout << (s1 == s2) << std::endl; // 输出 1
return 0;
}
优点:
- 实现了懒加载。
- 内存仅在需要时分配。
缺点:
- 默认情况下不是线程安全的。
- 在多线程环境下可能会产生多个实例。
3. 线程安全的懒汉式单例
特点:
- 使用互斥锁(
std::mutex
)保证线程安全。 - 适合多线程环境。
实现代码:
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
#include <iostream>
#include <mutex>
class Singleton {
private:
static Singleton* instance; // 静态指针
static std::mutex mutex; // 静态互斥锁
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex); // 加锁
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
std::cout << (s1 == s2) << std::endl; // 输出 1
return 0;
}
优点:
- 线程安全。
- 实现了懒加载。
缺点:
- 每次访问都需要加锁,性能可能较低。
4. 双重检查锁(Double-Checked Locking)
特点:
- 优化了线程安全的懒汉式单例,减少了锁的开销。
- 需要确保编译器支持内存屏障(C++11 及以上支持)。
实现代码:
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
#include <iostream>
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) { // 第二次检查
instance = new Singleton();
}
}
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
std::cout << (s1 == s2) << std::endl; // 输出 1
return 0;
}
优点:
- 线程安全。
- 仅在第一次访问时加锁,性能较高。
缺点:
- 实现复杂。
- 对编译器支持有一定要求(C++11 及以上)。
5. 使用 C++11 的 std::call_once
特点:
- 使用
std::call_once
和std::once_flag
,可以高效且线程安全地实现单例。 std::call_once
保证初始化代码只执行一次。
实现代码:
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
#include <iostream>
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::once_flag initFlag;
Singleton() {}
public:
static Singleton* getInstance() {
std::call_once(initFlag, []() { instance = new Singleton(); });
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
std::cout << (s1 == s2) << std::endl; // 输出 1
return 0;
}
优点:
- 线程安全。
- 代码简洁,高效。
缺点:
- 需要支持 C++11 及以上标准。
6. C++11 局部静态变量
特点:
- C++11 标准保证了函数内的局部静态变量在初始化时是线程安全的。
- 是实现单例模式最简单的方式之一。
实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
class Singleton {
private:
Singleton() {}
public:
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量
return instance;
}
};
int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
std::cout << (&s1 == &s2) << std::endl; // 输出 1
return 0;
}
优点:
- 简单易用。
- 线程安全。
- 延迟初始化。
缺点:
- 无法控制销毁时机(由静态变量的生命周期决定)。
实现方式的对比
实现方式 | 线程安全性 | 懒加载 | 实现复杂度 | 性能 |
---|---|---|---|---|
饿汉式单例 | 是 | 否 | 简单 | 高 |
懒汉式单例 | 否 | 是 | 简单 | 高(无锁情况下) |
线程安全的懒汉式单例 | 是 | 是 | 中等 | 低(每次加锁) |
双重检查锁 | 是 | 是 | 较复杂 | 高 |
std::call_once | 是 | 是 | 简单 | 高 |
局部静态变量 | 是 | 是 | 最简单 | 高 |
选择建议
- C++11 及以上:推荐使用 局部静态变量 或
std::call_once
。 - 需要控制懒加载:选择 线程安全的懒汉式单例 或 双重检查锁。
- 多线程性能敏感:选择 双重检查锁 或 局部静态变量。
策略模式实现
策略模式的实现
策略模式是一种行为型设计模式,它定义了一系列算法,并将每种算法封装起来,使它们可以互相替换,且使得算法的变化不会影响使用算法的客户端。
策略模式的结构
- 策略接口(Strategy) 定义所有策略的公共接口。
- 具体策略(Concrete Strategy) 实现策略接口,定义具体算法。
- 上下文(Context) 持有对策略对象的引用,使用策略接口来调用具体策略。
实现:支付系统示例
假设我们需要设计一个支付系统,支持多种支付方式(如信用卡支付、支付宝支付和微信支付),可以通过策略模式实现。
代码实现
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
76
77
#include <iostream>
#include <memory>
#include <string>
// 策略接口
class PaymentStrategy {
public:
virtual void pay(int amount) = 0; // 定义支付接口
virtual ~PaymentStrategy() {}
};
// 具体策略:信用卡支付
class CreditCardPayment : public PaymentStrategy {
private:
std::string cardNumber;
public:
CreditCardPayment(const std::string& cardNumber) : cardNumber(cardNumber) {}
void pay(int amount) override {
std::cout << "Paid " << amount << " using Credit Card (Card Number: " << cardNumber << ")." << std::endl;
}
};
// 具体策略:支付宝支付
class AlipayPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " using Alipay." << std::endl;
}
};
// 具体策略:微信支付
class WeChatPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " using WeChat." << std::endl;
}
};
// 上下文类
class PaymentContext {
private:
std::shared_ptr<PaymentStrategy> strategy; // 持有策略对象
public:
void setStrategy(const std::shared_ptr<PaymentStrategy>& newStrategy) {
strategy = newStrategy;
}
void executePayment(int amount) {
if (strategy) {
strategy->pay(amount); // 调用策略的支付方法
} else {
std::cout << "No payment strategy set!" << std::endl;
}
}
};
// 主函数
int main() {
PaymentContext context;
// 使用信用卡支付
context.setStrategy(std::make_shared<CreditCardPayment>("1234-5678-9876-5432"));
context.executePayment(100); // 输出:Paid 100 using Credit Card (Card Number: 1234-5678-9876-5432).
// 使用支付宝支付
context.setStrategy(std::make_shared<AlipayPayment>());
context.executePayment(200); // 输出:Paid 200 using Alipay.
// 使用微信支付
context.setStrategy(std::make_shared<WeChatPayment>());
context.executePayment(300); // 输出:Paid 300 using WeChat.
return 0;
}
代码解析
- 策略接口(
PaymentStrategy
) 定义了支付行为的接口,具体支付方式将实现该接口。 - 具体策略类
CreditCardPayment
:实现了信用卡支付逻辑。AlipayPayment
:实现了支付宝支付逻辑。WeChatPayment
:实现了微信支付逻辑。
- 上下文类(
PaymentContext
)- 持有策略对象,并通过
setStrategy
方法动态切换支付方式。 executePayment
调用策略对象的pay
方法执行支付。
- 持有策略对象,并通过
- 动态切换策略
- 上下文类的策略对象可以在运行时切换,使得客户端代码无需修改即可支持新策略。
优点
- 符合开闭原则:添加新策略时,无需修改现有代码。
- 行为独立:每种策略封装独立的算法,便于扩展和维护。
- 灵活性高:上下文可以在运行时选择或替换策略。
缺点
- 增加对象数量:每种策略都需要实现一个类,可能会增加代码复杂度。
- 上下文依赖策略接口:如果策略接口发生变化,需要同时修改上下文和具体策略类。
适用场景
- 算法需要动态切换
- 支付系统支持多种支付方式。
- 不同的排序算法(如快速排序、归并排序、冒泡排序)。
- 避免冗长的条件语句
- 如果代码中存在大量
if-else
或switch-case
,可以考虑使用策略模式。
- 如果代码中存在大量
- 行为需要独立封装
- 将算法封装为独立的策略类,便于复用和扩展。
总结
策略模式通过将算法封装为独立的类,使得算法可以互相替换,同时保持上下文代码的稳定性。在需要动态选择算法或避免冗长条件语句的场景中,策略模式是一个非常优秀的设计选择。