设计模式(c++面向对象核心)
一、什么是设计模式?为什么学它?
✅ 定义
设计模式(Design Patterns) 是在特定上下文中解决常见软件设计问题的可复用的解决方案模板。它不是可以直接复制粘贴的代码,而是经过验证的设计思想和最佳实践。
“软件设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过 分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码 更容易被他人理解并且保证代码可靠性。”
我们可用一句大白话:“在一定环境下,用固定套路解决问题。”

✅ 为什么学习设计模式(尤其对 C++ 开发者)?
| 好处 | 说明 |
|---|---|
| 提高代码质量 | 写出更清晰、模块化、低耦合、高内聚的代码 |
| 增强可维护性 | 易于修改、扩展和调试 |
| 提升沟通效率 | 团队中可以用“策略模式”、“观察者”等术语快速交流设计意图 |
| 避免重复造轮子 | 前人已总结出成熟解决方案,直接借鉴 |
| 理解大型框架 | STL、Boost、Qt、游戏引擎等广泛使用设计模式 |
二、GoF 的 23 种设计模式分类
1994 年,Erich Gamma 等四位作者(GoF)在《设计模式:可复用面向对象软件的基础》中提出 23 种经典模式,分为三类:
1. 创建型模式(Creational Patterns)
关注:如何创建对象,隐藏创建细节
| 模式 | 核心思想 | 典型应用场景 |
|---|---|---|
| 单例(Singleton) | 保证一个类只有一个实例 | 日志器、配置管理、线程池 |
| 工厂方法(Factory Method) | 子类决定创建哪个对象 | 框架中让子类扩展产品类型 |
| 抽象工厂(Abstract Factory) | 创建一系列相关对象 | 跨平台 UI 组件(WinButton, MacButton) |
| 建造者(Builder) | 分步构建复杂对象 | 构造配置复杂的对象(如 HTTP 请求) |
| 原型(Prototype) | 通过克隆创建新对象 | 对象创建成本高,需大量相似实例 |
2. 结构型模式(Structural Patterns)
关注:类或对象如何组合成更大的结构
| 模式 | 核心思想 | 典型应用场景 |
|---|---|---|
| 适配器(Adapter) | 转换接口,兼容不匹配的类 | 集成第三方库、旧系统升级 |
| 装饰器(Decorator) | 动态添加功能,替代继承 | I/O 流包装(BufferedStream)、UI 效果叠加 |
| 代理(Proxy) | 控制对对象的访问 | 远程代理、虚拟代理、保护代理 |
| 外观(Facade) | 提供统一高层接口 | 简化复杂子系统调用(如游戏引擎启动) |
| 桥接(Bridge) | 抽象与实现分离,独立变化 | 图形渲染 API + 不同平台实现 |
| 组合(Composite) | 树形结构,统一处理个体与整体 | 文件系统、菜单树、场景图 |
| 享元(Flyweight) | 共享细粒度对象,节省内存 | 文本编辑器中的字符格式、游戏中的子弹纹理 |
3. 行为型模式(Behavioral Patterns)
关注:对象间的职责分配与通信机制
| 模式 | 核心思想 | 典型应用场景 |
|---|---|---|
| 观察者(Observer) | 一对多依赖,状态变化自动通知 | 事件系统、MVC 模式、GUI 通知 |
| 策略(Strategy) | 封装算法,可动态切换 | 排序算法、支付方式、AI 行为 |
| 命令(Command) | 请求封装为对象 | 撤销/重做、任务队列、远程调用 |
| 状态(State) | 状态改变行为随之改变 | 有限状态机(游戏角色状态) |
| 模板方法(Template Method) | 父类定义流程,子类实现步骤 | 框架中固定算法骨架(如测试框架) |
| 迭代器(Iterator) | 统一访问聚合对象元素 | 遍历容器,隐藏内部结构 |
| 中介者(Mediator) | 减少对象间直接引用 | 复杂 UI 组件交互、聊天室 |
| 备忘录(Memento) | 捕获并恢复对象状态 | 撤销操作、游戏存档 |
| 访问者(Visitor) | 操作与数据结构分离 | 编译器 AST 遍历、报表生成 |



三、设计模式基本原则(面向对象设计基石)
最终目的:高内聚,低耦合;所有设计模式本质上都是为了更好地实践这些设计原则。
1) 开放封闭原则 (OCP,Open For Extension, Closed For Modification Principle)
类的改动是通过增加代码进行的,而不是修改源代码。
2) 单一职责原则 (SRP,Single Responsibility Principle)
类的职责要单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。
3) 依赖倒置原则 (DIP,Dependence Inversion Principle)
依赖于抽象(接口),不要依赖具体的实现(类),也就是针对接口编程。
4) 接口隔离原则 (ISP,Interface Segegation Principle)
不应该强迫客户的程序依赖他们不需要的接口方法。一个接口应该只提供一种对外功能,不应该把所有操作都封装到一个接口中去。
5) 里氏替换原则 (LSP, Liskov Substitution Principle)
任何抽象类出现的地方都可以用他的实现类进行替换。实际就是虚拟机制,语言级别实现面向对象功能。
6) 优先使用组合而不是继承原则(CARP,Composite/Aggregate Reuse Principle)
如果使用继承,会导致父类的任何变换都可能影响到子类的行为。
如果使用对象组合,就降低了这种依赖关系。
7) 迪米特法则(LOD,Law of Demeter)
一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。例如在一个程序中,各个模块之间相互调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解另外一个模块的内部实现细节,这样当一个模块内部的实现发生改变时,不会影响其他模块的使用。(黑盒原理)
1. 单一职责原则(SRP: Single Responsibility Principle)
✅ 定义:一个类应该只有一个引起它变化的原因,即只负责一项职责。
📌 通俗理解:一个类只做一件事,做好这件事。
❌ 反例(违反 SRP)
class User {
public:
void setName(const std::string& name) { this->name = name; }
void saveToDatabase(); // 职责1:业务数据管理
void sendEmail(const std::string& msg); // 职责2:网络通信
private:
std::string name;
};
👉 User 类既管数据又管发邮件,如果数据库结构或邮件协议变了,都要改这个类。
✅ 正确做法
class User {
// 只负责用户数据
};
class UserRepository {
public:
void save(const User& user); // 负责持久化
};
class EmailService {
public:
void send(const std::string& to, const std::string& msg); // 负责发邮件
};
2. 开闭原则(OCP: Open/Closed Principle)
✅ 定义:对扩展开放,对修改关闭。
📌 通俗理解:当需求变化时,应通过添加新代码来实现,而不是修改已有代码。
✅ 示例:使用多态实现 OCP
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double r;
public:
double area() const override { return 3.14 * r * r; }
};
class Rectangle : public Shape {
double w, h;
public:
double area() const override { return w * h; }
};
// 计算总面积的函数无需修改
double totalArea(const std::vector<Shape*>& shapes) {
double sum = 0;
for (auto* s : shapes) sum += s->area();
return sum;
}
👉 新增 Triangle 类时,只需新增类,无需修改 totalArea 函数。

3. 里氏替换原则(LSP: Liskov Substitution Principle)
✅ 定义:子类对象必须能够替换其父类对象,而程序行为不变。
📌 通俗理解:继承不是为了复用代码,而是为了多态;子类不能“破坏”父类的行为契约。
❌ 反例(违反 LSP)
class Rectangle {
protected:
int width, height;
public:
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int area() const { return width * height; }
};
class Square : public Rectangle {
public:
void setWidth(int w) override { width = height = w; }
void setHeight(int h) override { width = height = h; }
};
👉 如果函数期望一个 Rectangle,传入 Square 可能导致面积计算错误(因为宽高联动)。
✅ 解决方案
重构继承关系,或使用组合。
4. 接口隔离原则(ISP: Interface Segregation Principle)
✅ 定义:客户端不应依赖它不需要的接口。应将大接口拆分为多个小接口。
📌 通俗理解:不要强迫实现类实现它用不到的方法。
❌ 反例
class Machine {
public:
virtual void print() = 0;
virtual void scan() = 0;
virtual void fax() = 0;
};
class OldPrinter : public Machine {
public:
void print() override { /* OK */ }
void scan() override { throw std::runtime_error("Not supported!"); } // 强迫实现
void fax() override { throw std::runtime_error("Not supported!"); }
};
✅ 正确做法
struct Printable {
virtual void print() = 0;
};
struct Scannable {
virtual void scan() = 0;
};
struct Faxable {
virtual void fax() = 0;
};
class ModernPrinter : public Printable, public Scannable {
public:
void print() override { /* ... */ }
void scan() override { /* ... */ }
};
5. 依赖倒置原则(DIP: Dependency Inversion Principle)
✅ 定义:
- 高层模块不应依赖低层模块,二者都应依赖抽象。
- 抽象不应依赖细节,细节应依赖抽象。
📌 通俗理解:面向接口编程,而不是面向实现编程。
❌ 反例(高层依赖低层)
class MySQLDatabase {
public:
void connect() { /* MySQL 连接 */ }
};
class UserService {
MySQLDatabase db; // 直接依赖具体实现
public:
void addUser() {
db.connect();
// ...
}
};
👉 如果换为 PostgreSQL,UserService 必须修改。
✅ 正确做法(依赖抽象)
class Database {
public:
virtual void connect() = 0;
virtual ~Database() = default;
};
class MySQLDatabase : public Database { /* 实现 */ };
class PostgreSQLDatabase : public Database { /* 实现 */ };
class UserService {
Database* db; // 依赖抽象
public:
UserService(Database* d) : db(d) {}
void addUser() { db->connect(); }
};
👉 通过构造函数注入依赖,符合 DIP,也便于测试(可用 Mock)。


6. 迪米特法则(LoD: Law of Demeter) / 最少知识原则
✅ 定义:一个对象应该对其他对象保持最少的了解。只和“朋友”说话。
📌 通俗理解:不要“链式调用”过深,如
a.getB().getC().doSomething()应避免。
✅ 示例
// ❌ 违反
user.getWallet().getBalance().getValue();
// ✅ 改进
user.getBalance(); // 封装细节


7. 合成/聚合复用原则(CARP: Composite Reuse Principle)
✅ 定义:尽量使用组合/聚合,而不是继承来达到复用的目的。
📌 通俗理解:优先用“有一个”(has-a),而不是“是一个”(is-a)。
✅ 示例
class Engine {
public:
void start() { /* 启动引擎 */ }
};
class Car {
Engine engine; // 组合
public:
void start() { engine.start(); }
};
👉 比继承 Engine 更灵活,易于更换引擎类型。
四、设计原则与设计模式的关系
| 设计模式 | 主要遵循的原则 |
|---|---|
| 单例模式 | SRP(单一职责) |
| 工厂模式 | DIP、OCP(依赖抽象,易于扩展) |
| 策略模式 | OCP、DIP(算法可替换) |
| 观察者模式 | DIP、OCP(观察者依赖抽象) |
| 装饰器模式 | OCP(动态扩展功能) |
| 适配器模式 | DIP、ISP(转换接口) |
🔁 所有设计模式本质上都是为了更好地实践这些设计原则。
五、C++ 实现设计模式的关键特性支持
C++ 的强大之处在于它能以多种方式实现同一模式,灵活性极高:
| C++ 特性 | 在设计模式中的应用 |
|---|---|
| 虚函数 / 多态 | 实现运行时多态(如工厂、策略、命令) |
| 模板(泛型) | 实现编译时多态,性能更高(策略模式可用模板) |
| 智能指针 | 自动管理对象生命周期(避免单例内存泄漏) |
| std::function & lambda | 简化命令、策略模式的实现 |
| RAII | 确保资源安全释放(如代理模式中的锁) |
| 运算符重载 | 增强接口一致性(如迭代器) |
更多推荐


所有评论(0)