写给新手的详细设计模式入门手册,从原理到代码,手把手带你吃透几种常用设计模式。
前言:为什么要学设计模式?
如果你是一个刚入门的 C++ 开发者,你可能会问:" 我会写for循环,会用类和对象,能把功能跑通不就行了吗?为什么要学什么设计模式?"
别急,我们来看一个真实的场景:
假设你写了一个计算器程序,一开始只支持加法。代码很简单:
1
2
3
4
5
|
int calculate(int a, int b, string op) {
if (op == "add") {
return a + b;
}
}
|
后来你加了减法:
1
2
3
4
5
6
7
|
int calculate(int a, int b, string op) {
if (op == "add") {
return a + b;
} else if (op == "sub") {
return a - b;
}
}
|
再加乘法、除法… 最后这个calculate函数变得几百行,每次加新功能都要改这个函数,改着改着就改错了,而且测试起来也特别麻烦。
这就是坏代码的典型特征:
-
耦合度高:所有逻辑都堆在一起,改一处影响全身
-
可维护性差:加新功能要改老代码,容易出 Bug
-
可读性差:几百行的函数,谁看谁头疼
而设计模式,就是前辈们总结出来的,解决这类常见问题的 “最佳实践”。它不是什么黑魔法,只是一套让代码变得优雅、可维护、可扩展的通用套路。
一、设计模式入门:先搞懂这几个概念
1.1 什么是设计模式?
设计模式(Design Pattern)是对软件设计中普遍存在的各种问题,所提出的解决方案。
简单来说:
-
它不是代码,而是解决问题的思路
-
它不是标准,而是最佳实践
-
它不是必须的,但学会了能让你少走很多弯路
1994 年,四位大牛(GoF, Gang of Four)出版了《设计模式:可复用面向对象软件的基础》,总结了 23 种经典的设计模式,这也是我们今天要学习的核心内容。
1.2 23 种模式的分类
这 23 种模式并不是杂乱无章的,它们可以分为三大类:
1. 创建型模式(5 种)
2. 结构型模式(7 种)
3. 行为型模式(11 种)
1.3 设计模式的基石:SOLID 原则
在学习具体的模式之前,你必须先理解这 5 个基本原则,因为所有的设计模式,本质上都是为了更好地遵守这几个原则。
1. 单一职责原则(SRP)
一个类,只负责一件事。
比如,一个User类,就只负责用户的数据;不要让它同时负责 “把用户数据存到数据库”,那是UserRepository的事。
好处:改需求的时候,你只需要改对应的类,不会影响其他地方。
2. 开闭原则(OCP)
对扩展开放,对修改关闭。
意思是:加新功能的时候,你应该加新的类 / 新的代码,而不是去改已经写好的、测试过的老代码。
这是最重要的一个原则!比如你要加新的支付方式,加个新类就行了,不用去改原来的支付逻辑。
3. 里氏替换原则(LSP)
子类必须能替换掉它的父类,程序运行结果不变。
比如,父类是Bird,有个fly()方法;子类Penguin(企鹅)也是鸟,但它不会飞。如果你强行让Penguin继承Bird,那调用fly()就会报错,这就违反了 LSP。
你可以把Bird拆成两个接口:Flyable(会飞的鸟)和NonFlyable(不会飞的鸟),这样就不会有问题了。
1
2
3
4
5
6
7
8
9
|
class Flyable {
public:
virtual void fly() = 0;
};
class NonFlyable {
public:
virtual void walk() = 0;
};
|
4. 接口隔离原则(ISP)
不要强迫客户端依赖它不需要的接口。
简单说:接口要小而精,不要搞一个大而全的胖接口。比如,你不要搞一个Animal接口,里面既有eat()、sleep(),又有fly()、swim(),因为不是所有动物都会飞会游泳。
你可以把接口拆成Eater、Sleeper、Flyer、Swimmer,每个类只实现它需要的接口,这样就更清晰了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Eater {
public:
virtual void eat() = 0;
};
class Sleeper {
public:
virtual void sleep() = 0;
};
class Flyer {
public:
virtual void fly() = 0;
};
class Swimmer {
public:
virtual void swim() = 0;
};
|
5. 依赖倒置原则(DIP)
依赖抽象,不要依赖具体实现。
高层模块不要依赖低层模块,两者都要依赖抽象。
比如,你开车,你只需要依赖 “车” 这个抽象(它能跑),而不要依赖具体是 “宝马” 还是 “特斯拉”。这样你换车的时候,完全不用改你开车的代码。
二、创建型模式:搞定对象的创建
2.1 单例模式(Singleton):全局唯一的实例
它解决了什么问题?
有时候,我们需要一个类,在整个程序里,只有一个实例。
比如:
你肯定不想让别人随便new一个配置管理器,不然就会有好几个配置,数据就乱了。
核心思路
-
把构造函数私有化,不让别人随便new
-
自己在类里面维护唯一的实例
-
提供一个静态方法,让别人通过这个方法获取唯一的实例
UML 类图
C++ 代码实现(推荐!线程安全版)
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
32
33
34
35
36
37
38
39
40
41
42
43
|
#include <iostream>
#include <string>
class Logger {
private:
// 1. 私有构造函数!禁止外部创建实例
Logger() {
std::cout << "Logger 实例创建了,只会执行一次!" << std::endl;
}
// 2. 禁止拷贝构造和赋值操作,防止别人复制出一个新实例
Logger(const Logger&) = delete; // 禁止拷贝构造
Logger& operator=(const Logger&) = delete; // 禁止赋值操作
public:
// 3.全局访问点,获取唯一的实例
static Logger& getInstance() {
// C++11 之后,局部静态变量的初始化是线程安全的,保证了单例模式在多线程环境下的正确性
static Logger instance; // 局部静态变量,第一次调用时创建实例,之后直接返回
return instance;
}
// 业务方法:打印日志
void log(const std::string& message) {
std::cout << "日志: " << message << std::endl;
}
};
// 测试单例模式
int main() {
// 不管你调用多少次getInstance(),都只会创建一个Logger实例
Logger::getInstance().log("程序启动了!");
Logger::getInstance().log("用户登录了!");
Logger& l1 = Logger::getInstance();
Logger& l2 = Logger::getInstance();
// 如果输出的地址相同,说明l1和l2是同一个实例
std::cout << "l1的地址: " << &l1 << std::endl;
std::cout << "l2的地址: " << &l2 << std::endl;
return 0;
}
|
运行结果
1
2
3
4
5
|
Logger 实例创建了,只会执行一次!
日志: 程序启动了!
日志: 用户登录了!
l1的地址: 0x7ff7c68530b0
l2的地址: 0x7ff7c68530b0
|
注意事项
2.2 工厂模式(Factory):对象创建的 “代工厂”
它解决了什么问题?
如果你的代码里到处都是new具体的类,比如:
1
2
3
4
5
6
7
|
if (type == "word") {
doc = new WordDocument();
} else if (type == "pdf") {
doc = new PdfDocument();
} else if (type == "txt") {
doc = new TxtDocument();
}
|
每次加新的文档类型,你都要改这段代码,而且这段代码会散落在你的项目里,维护起来巨麻烦。
工厂模式就是把这些new的逻辑,统一放到一个 “工厂” 里,你要什么对象,找工厂要就行了,不用自己new。
工厂方法模式:最常用的工厂
工厂方法模式,就是给每个产品,配一个对应的工厂,完美符合开闭原则!
UML 类图
C++ 代码实现
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
|
#include <iostream>
#include <memory> // 引入智能指针头文件,使用std::unique_ptr来管理资源,避免内存泄漏。
// ====================== 第一步:定义产品接口 ======================
// 所有文档都要实现这个接口
class Document {
public:
virtual ~Document() = default; // 虚析构函数,确保子类能正确析构
virtual void open() = 0; // 打开文档,子类必须实现
};
// 具体产品1:Word文档
class WordDoc : public Document {
public:
void open() override {
std::cout << "正在打开Word文档: xxx.docx" << std::endl;
}
};
// 具体产品2:PDF文档
class PDFDoc : public Document {
public:
void open() override {
std::cout << "正在打开PDF文档: xxx.pdf" << std::endl;
}
};
// ====================== 第二步:定义工厂接口 ======================
class DocumentFactory {
public:
virtual ~DocumentFactory() = default; // 虚析构函数,确保子类能正确析构
virtual std::unique_ptr<Document> createDocument() = 0; // 创建文档,子类必须实现
};
// 具体工厂1:Word文档工厂
class WordDocFactory : public DocumentFactory {
public:
std::unique_ptr<Document> createDocument() override {
return std::make_unique<WordDoc>(); // 创建并返回一个Word文档实例
}
};
// 具体工厂2:PDF文档工厂
class PDFDocFactory : public DocumentFactory {
public:
std::unique_ptr<Document> createDocument() override {
return std::make_unique<PDFDoc>(); // 创建并返回一个PDF文档实例
}
};
// ====================== 第三步:使用工厂创建产品 ======================
// 客户端只依赖工厂接口,不直接依赖具体产品
void clientCode(std::unique_ptr<DocumentFactory> factory) {
auto doc = factory->createDocument(); // 通过工厂创建文档
doc->open(); // 打开文档
}
int main() {
// 我要打开Word文档,就传Word工厂
clientCode(std::make_unique<WordDocFactory>());
// 我要打开PDF文档,就传PDF工厂
clientCode(std::make_unique<PDFDocFactory>());
// 以后我要加TXT文档?
// 只需要加一个TxtDoc类,再加一个TxtDocFactory类!
// 完全不用改clientCode里的代码!完美符合开闭原则!
return 0;
}
// 这样的设计使得我们可以轻松地添加新的文档类型,而不需要修改现有的代码,符合开闭原则(Open/Closed Principle)。每个工厂负责创建一个特定类型的文档,客户端代码只依赖于工厂接口,不需要知道具体的文档实现细节。
|
运行结果
1
2
|
正在打开Word文档:xxx.docx
正在打开PDF文档:xxx.pdf
|
什么时候用工厂模式?
-
你不知道运行时到底要创建哪个类的实例
-
你想把对象的创建逻辑封装起来,统一管理
-
你希望扩展新产品的时候,不用改老代码
三、结构型模式:搞定类的组合
3.1 适配器模式(Adapter):给旧接口做个 “转接头”
它解决了什么问题?
你有没有过这种情况:你有一个旧的类,它的接口和你现在系统需要的接口不兼容,但是你又不能改旧的代码(比如它是第三方库)。
这时候你就需要一个转接头,把旧接口转换成新接口,这就是适配器模式。
举个例子:
-
旧的打印机有个方法叫printOld(string s)
-
新系统需要的打印机接口是print(string s)
-
你搞个适配器,把print的调用,转成调用旧的printOld
UML 类图
C++ 代码实现
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 <string>
// 新系统期望的接口
class Printer {
public:
virtual ~Printer() = default;
virtual void print(const std::string& content) = 0;
};
// 旧系统的类,接口不兼容
class LegacyPrinter {
public:
// 旧的接口,与新的接口不兼容
void printOld(const std::string& content) {
std::cout << "[旧系统打印]" << content << std::endl;
}
};
// 适配器,实现新的接口,同时持有旧系统的实例
class PrinterAdapter : public Printer {
private:
LegacyPrinter legacyPrinter; // 持有旧的打印机
public:
// 实现新的接口
void print(const std::string& content) override {
// 把新接口的调用,转发给旧的打印机
legacyPrinter.printOld(content);
}
};
// 客户端代码,只认识新的Printer接口
void usePrinter(Printer& printer) {
printer.print("Hello, Adapter!");
}
int main() {
PrinterAdapter adapter; // 创建适配器实例
usePrinter(adapter); // 客户端使用适配器,实际上调用的是旧系统的打印机
return 0;
}
// 这样做的好处是,客户端代码只依赖于新的接口(Printer),而不需要修改旧系统的代码(LegacyPrinter)。适配器模式使得两个不兼容的接口能够协同工作,增加了系统的灵活性和可扩展性。
|
运行结果
1
|
[旧系统打印] Hello, Adapter!
|
3.2 装饰器模式(Decorator):动态给对象 “加 buff”
它解决了什么问题?
如果你想给一个对象动态地添加功能,用继承的话,会导致 “类爆炸”。
比如,咖啡:
-
基础咖啡
-
咖啡 + 牛奶
-
咖啡 + 糖
-
咖啡 + 牛奶 + 糖
-
咖啡 + 奶泡
-
…
如果你用继承,你要写 N 多个子类:CoffeeWithMilk、CoffeeWithSugar、CoffeeWithMilkAndSugar… 这也太蠢了。
装饰器模式就是解决这个的:你可以动态地给对象叠加功能,想加什么加什么。
UML 类图
C++ 代码实现
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
|
#include <iostream>
#include <string>
#include <memory>
// 咖啡的抽象接口
class Coffee {
public:
virtual ~Coffee() = default;
virtual std::string getDescription() = 0;
virtual double getPrice() = 0;
};
// 基础咖啡:浓缩咖啡
class Espresso : public Coffee {
public:
std::string getDescription() override {
return "浓缩咖啡";
}
double getPrice() override {
return 25.0; // 基础价25
}
};
// 装饰器的基类,是一个抽象类,继承自Coffee,并持有一个Coffee对象的指针
class CoffeeDecorator : public Coffee {
protected: // 保护成员,允许子类访问
std::unique_ptr<Coffee> coffee; // 持有被装饰的咖啡
public:
CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};
// 具体的装饰器:加牛奶
class MilkDecorator : public CoffeeDecorator {
public:
MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
std::string getDescription() override {
return coffee->getDescription() + ", 加牛奶";
}
double getPrice() override {
return coffee->getPrice() + 5.0; // 牛奶加5元
}
};
// 具体的装饰器:加糖
class SugarDecorator : public CoffeeDecorator {
public:
SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
std::string getDescription() override {
return coffee->getDescription() + ", 加糖";
}
double getPrice() override {
return coffee->getPrice() + 2.0; // 加糖2元
}
};
int main() {
// 1. 先来一杯基础浓缩
std::unique_ptr<Coffee> coffee = std::make_unique<Espresso>();
std::cout << coffee->getDescription() << " | 价格:" << coffee->getPrice() << std::endl;
// 2. 加牛奶
coffee = std::make_unique<MilkDecorator>(std::move(coffee)); // 用装饰器包装原来的咖啡,把原来的咖啡转移到装饰器中
std::cout << coffee->getDescription() << " | 价格:" << coffee->getPrice() << std::endl;
// 3. 再加糖
coffee = std::make_unique<SugarDecorator>(std::move(coffee)); // 再用装饰器包装一次
std::cout << coffee->getDescription() << " | 价格:" << coffee->getPrice() << std::endl;
}
// 这样,我们通过装饰器模式,动态地为基础咖啡添加了牛奶和糖的功能,同时保持了代码的清晰和可维护性。每个装饰器都只关注自己添加的功能,而不需要修改原有的咖啡类,这符合开闭原则。
|
运行结果
1
2
3
|
浓缩咖啡 | 价格: 25
浓缩咖啡, 加牛奶 | 价格: 30
浓缩咖啡, 加牛奶, 加糖 | 价格: 32
|
四、行为型模式:搞定对象的交互
4.1 观察者模式(Observer):一对多的 “订阅 - 发布”
它解决了什么问题?
当一个对象的状态发生变化时,你要自动通知所有依赖它的对象,让它们更新。
这就是我们常说的发布 - 订阅模式!
比如:
UML 类图
C++ 代码实现
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <algorithm> // std::remove_if用于从vector中移除元素
// 前向声明
class Subject;
// 观察者接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update(Subject* subject) = 0; // 收到通知时更新
};
// 主题(被观察者)接口
class Subject {
private:
// 保存所有的观察者,用weak_ptr避免循环引用,当观察者被销毁时,weak_ptr会自动失效
std::vector<std::weak_ptr<Observer>> observers;
public:
virtual ~Subject() = default;
// 订阅
void attach(std::shared_ptr<Observer> observer) {
observers.push_back(observer);
}
// 取消订阅
void detach(std::shared_ptr<Observer> observer) {
// 使用remove_if和erase从observers中移除与observer相同的元素,wp.lock() == observer表示找到了要移除的观察者
observers.erase(
std::remove_if(observers.begin(),observers.end(),
[&](const std::weak_ptr<Observer>& wp) {
return wp.lock() == observer;
}),
observers.end()
);
}
// 通知所有观察者
void notify() {
// 遍历所有观察者,调用它们的update方法
for (auto& wp : observers) {
if (auto observer = wp.lock()) {
observer->update(this);
}
}
}
// 状态接口
virtual std::string getState() const = 0;
virtual void setState(const std::string& state) = 0;
};
// 具体的主题:气象站
class WeatherStation : public Subject {
private:
std::string weatherState;
public:
std::string getState() const override {
return weatherState;
}
void setState(const std::string& state) override {
weatherState = state;
notify(); // 状态一变,立刻通知所有人!
}
};
// 具体的观察者
// 观察者1:当前天气显示器
class CurrentDisplay : public Observer {
public:
void update(Subject* subject) override {
std::cout << "【当前天气】:" << subject->getState() << std::endl;
}
};
// 观察者2:天气预报显示器
class ForecastDisplay : public Observer {
public:
void update(Subject* subject) override {
std::string state = subject->getState();
std::string forecast = (state == "晴天") ? "明天也是晴天!" : "明天可能下雨,记得带伞";
std::cout << "【天气预报】:" << forecast << std::endl;
}
};
int main() {
// 创建气象站
std::shared_ptr<Subject> station = std::make_unique<WeatherStation>();
// 创建观察者
std::shared_ptr<Observer> current = std::make_unique<CurrentDisplay>();
std::shared_ptr<Observer> forecast = std::make_unique<ForecastDisplay>();
// 订阅
station->attach(current);
station->attach(forecast);
std::cout << "=== 气象站更新:晴天 ===" << std::endl;
station->setState("晴天");
// 小明取消了天气预报的订阅
station->detach(forecast);
std::cout << "=== 气象站更新:雨天 ===" << std::endl;
station->setState("雨天");
return 0;
}
// 这样的设计使得观察者和主题之间的耦合度非常低,观察者可以自由地订阅和取消订阅,而主题只需要负责通知观察者即可。观察者模式非常适合实现事件驱动的系统,比如 GUI 应用、消息系统等。当主题的状态发生变化时,所有依赖它的观察者都会自动收到通知并更新自己,这极大地提高了系统的灵活性和可维护性。
|
运行结果
1
2
3
4
5
6
|
=== 气象站更新:晴天 ===
【当前天气】: 晴天
【天气预报】: 明天也是晴天!
=== 气象站更新:雨天 ===
【当前天气】: 雨天
|
4.2 策略模式(Strategy):动态切换算法
它解决了什么问题?
如果你有多个算法,你想在运行的时候动态地切换它们,而且让算法的变化独立于使用算法的客户。
比如:
如果不用策略模式,你可能会写一堆if-else:
1
2
3
4
5
6
7
|
if (userType == "normal") {
price = p;
} else if (userType == "vip") {
price = p * 0.9;
} else if (userType == "svip") {
price = p * 0.8;
}
|
加新的折扣类型,又要改这个函数,太麻烦了。
策略模式就是把每个算法,封装成独立的类,然后你想换哪个就换哪个。
UML 类图
C++ 代码实现
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 DiscountStrategy {
public:
virtual ~DiscountStrategy() = default;
virtual double calculate(double originalPrice) = 0;
};
// 具体的策略
// 普通用户:无折扣
class NormalDiscount : public DiscountStrategy {
public:
double calculate(double originalPrice) override {
std::cout << "普通用户,无折扣" << std::endl;
return originalPrice;
}
};
// VIP用户:9折
class VipDiscount : public DiscountStrategy {
public:
double calculate(double originalPrice) override {
std::cout << "VIP用户,打9折" << std::endl;
return 0.9 * originalPrice;
}
};
// 超级VIP:8折
class SvipDiscount : public DiscountStrategy {
public:
double calculate(double originalPrice) override {
std::cout << "超级VIP用户,打8折" << std::endl;
return 0.8 * originalPrice;
}
};
// 上下文:使用策略的类
class PriceCalculator {
private:
std::unique_ptr<DiscountStrategy> strategy; // 当前用的策略
public:
// 构造的时候传入策略
PriceCalculator(std::unique_ptr<DiscountStrategy> s) : strategy(std::move(s)) {}
// 动态切换策略
void setStrategy(std::unique_ptr<DiscountStrategy> s) {
strategy = std::move(s);
}
// 计算最终价格
double getFinalPrice(double originalPrice) {
return strategy->calculate(originalPrice);
}
};
int main() {
// 一开始,默认普通用户
PriceCalculator calculator(std::make_unique<NormalDiscount>());
double normalPrice = calculator.getFinalPrice(100);
std::cout << "原价100元,最终价格:" << normalPrice << std::endl;
// 用户升级成VIP了,切换策略
calculator.setStrategy(std::make_unique<VipDiscount>());
double vipPrice = calculator.getFinalPrice(100);
std::cout << "原价100元,最终价格:" << vipPrice << std::endl;
// 用户升级成超级VIP了,切换策略
calculator.setStrategy(std::make_unique<SvipDiscount>());
double svipPrice = calculator.getFinalPrice(100);
std::cout << "原价100元,最终价格:" << svipPrice << std::endl;
// 以后要加新的折扣?比如节日折扣?
// 加个HolidayDiscount类就行了!完全不用改Calculator的代码!
return 0;
}
|
运行结果
1
2
3
4
5
6
|
普通用户,无折扣
原价100元,最终价格: 100
VIP用户,打9折
原价100元,最终价格: 90
超级VIP,打8折
原价100元,最终价格: 80
|
五、怎么学好设计模式?
5.1 不要死记硬背
很多人学设计模式,就是把 23 个模式的定义背下来,这完全没用!
你要记住的是:
-
这个模式解决了什么问题?
-
什么时候该用它?
-
什么时候不该用它?
5.2 先写坏代码,再重构
最好的学习方式,不是上来就用设计模式。
-
先把功能写出来,哪怕代码很烂,一堆if-else
-
当你发现代码越来越难维护的时候,再回头看,哪个模式能解决你的问题
-
重构!
这样你才能真正理解,这个模式解决了什么痛点。
5.3 不要过度设计
设计模式不是银弹!不要为了用模式而用模式。
5.4 从常用的模式开始学
不用一上来就把 23 个都学完,先把这几个最常用的吃透:
-
单例(最简单,必学)
-
工厂(解耦创建,必学)
-
观察者(事件系统,必学)
-
策略(算法切换,必学)
-
适配器(兼容旧代码,常用)
-
装饰器(动态加功能,常用)
剩下的,用到的时候再学也不迟。
总结
设计模式,本质上就是一套 “让代码变好” 的经验总结。它不是什么高深的黑魔法,而是帮你写出更优雅、更易维护的代码的工具。
作为小白,你不用急着一口吃成胖子,慢慢来:
-
先理解 SOLID 原则
-
逐个吃透常用的模式,动手敲代码
-
在实际项目中,遇到问题了,想想哪个模式能帮你
相信我,当你真正理解了设计模式,你会发现,写代码原来是一件这么爽的事!
本文章基于现代 C++(C++11 及以上)编写,所有代码均可直接编译运行。