C++学习笔记DAY6
C++ 设计模式
整体学下来的话有点云里雾里的,回头看的话好像并没有说什么新的内容,更多的是如何把之前学的封装/继承/多态等进行组合,去进行一种蓝图的绘制。这样看下来的话似乎需要更多的代码训练而不只是理论学习,,当然需要时常温故才能知新。
缓慢补笔记ing
目录
一、UML 统一建模语言
1.1 UML 概述
UML(Unified Modeling Language) 是一种通用、可视化的面向对象建模语言,提供统一的、标准化的图形化设计语言和工具集,适用于软件工程领域的分析与设计。
UML 常用图表及其作用:
| 图表类型 | 中文名 | 作用 |
|---|---|---|
| Class Diagram | 类图 | 描述系统中类的内部结构以及类与类之间的关系(静态结构) |
| Sequence Diagram | 时序图 | 描述对象之间动态交互关系,展现消息在时间轴上的传递顺序 |
| Collaboration Diagram | 协作图 | 展现对象之间的协作关系,描述消息交互和执行顺序 |
时序图 vs 协作图:两者都描述对象间的交互,时序图强调时间顺序,协作图强调组织结构(上下文关系)。
1.2 类图(Class Diagram)
1.2.1 类的表示
UML 类图用一个三栏矩形表示:
┌─────────────────┐
│ ClassName │ ← 类名
├─────────────────┤
│ + public成员 │ ← 属性栏(+ 公有, # 保护, - 私有)
│ # protected成员 │
│ - private成员 │
├─────────────────┤
│ + publicMethod() │ ← 方法栏
│ # protectedMethod()│
│ - privateMethod() │
└─────────────────┘
访问修饰符:
| 符号 | 含义 | C++ 对应 |
|---|---|---|
+ |
公有 | public |
# |
保护 | protected |
- |
私有 | private |
1.2.2 类与类之间的六种关系
从弱到强:依赖 → 关联 → 聚合 → 组合 → 实现 → 泛化
关系强度:依赖 < 关联 < 聚合 < 组合 < 实现/泛化
① 泛化(继承)关系 — 子类继承父类
┌──────────┐
│ Vehicle │ (父类)
└─────◇────┘ ← 空心三角箭头指向父类
│
┌─────┴────┐
│ Truck │ (子类)
└──────────┘
class Vehicle {
protected:
string brand;
};
class Truck : public Vehicle { // 继承
private:
string type;
};
② 关联关系 — 类与类之间的引用
┌──────────────┐ ┌──────────────┐
│ Department │ ──────▶ │ Teacher │
└──────────────┘ └──────────────┘
class Teacher {
private:
string name;
};
class Department {
private:
string name;
Teacher* teacher; // 关联关系:Department 持有 Teacher 的引用
};
③ 聚合关系(Aggregation) — 整体与部分,但部分可独立存在
┌──────────────┐◇───────────────┌──────────────┐
│ University │ (空心菱形) │ College │
└──────────────┘ └──────────────┘
class College {
private:
string name;
};
class University {
private:
string name;
College* colleges[]; // 聚合:College 可以脱离 University 独立存在
};
聚合特点:has-a 关系,但部分的生命周期不依赖整体。如大学和学院——大学解散了学院可以并到其他大学。
④ 组合关系(Composition) — 整体与部分,部分不能独立存在
┌──────────────┐◆───────────────┌──────────────┐
│ Triangle │ (实心菱形) │ Side │
└──────────────┘ └──────────────┘
class Side {
private:
double length;
};
class Triangle {
private:
Side side1; // 组合:Side 的生命周期与 Triangle 绑定
Side side2;
Side side3;
};
组合特点:contains-a 关系,部分的生命周期依赖整体。如三角形和边——三角形销毁了边也就不存在了。
聚合 vs 组合对比:
| 特性 | 聚合 | 组合 |
|---|---|---|
| UML 符号 | 空心菱形 ◇ | 实心菱形 ◆ |
| 部分能否独立存在 | ✅ 能 | ❌ 不能 |
| 生命周期 | 独立 | 绑定(同生共死) |
| 示例 | 大学与学院 | 三角形与边 |
| 代码特征 | 指针/引用持有 | 值成员持有 |
⑤ 实现关系(Realization) — 类实现接口
┌──────────────┐┊┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄▶┌──────────────┐
│ ConcreteClass│ (空心三角虚线) │ Interface │
└──────────────┘ └──────────────┘
class IShape { // 接口(抽象类)
public:
virtual void draw() = 0;
};
class Circle : public IShape { // 实现
public:
void draw() override { /* ... */ }
};
1.3 时序图(Sequence Diagram)
时序图描述了系统中对象之间的动态交互关系,展现了消息在时间轴上的传递顺序。
时序图要素:
| 要素 | 说明 |
|---|---|
| 对象 | 矩形框中写有对象名和/或类名,名字下面有下划线 |
| 生命线 | 纵向虚线,表示对象在序列中的执行情况 |
| 消息线 | 对象生命线之间的水平箭头 |
| 消息类型 | 同步(实心箭头)、异步(开放箭头)、返回(虚线箭头) |
阅读方式:从上到下查看对象间交换的消息,分析随时间流逝而发生的消息交换。
Client Factory Product
│ │ │
│──create─────▶│ │
│ │──new─────────▶│
│ │◀──Product*────│
│◀──Product*───│ │
│──use()──────▶│ │
│ │ │
1.4 协作图(Collaboration Diagram)
协作图(也叫通信图)强调发送和接收消息的对象之间的组织结构。
- 显示了一系列的对象和它们之间的联系
- 使用编号来表示消息的执行顺序(如 1, 1.1, 1.1.1)
- 如果需要强调时间和序列 → 选择时序图
- 如果需要强调上下文结构 → 选择协作图
时序图和协作图在语义上是等价的,可以互相转换。
二、面向对象设计原则
2.1 SOLID 原则总览
| 首字母 | 原则 | 中文名 | 一句话总结 |
|---|---|---|---|
| S | Single Responsibility | 单一职责 | 一个类只做一件事 |
| O | Open/Closed | 开闭原则 | 对扩展开放,对修改关闭 |
| L | Liskov Substitution | 里氏替换 | 子类可以完美替换父类 |
| I | Interface Segregation | 接口隔离 | 接口要专用,不要臃肿 |
| D | Dependency Inversion | 依赖倒置 | 依赖抽象,不依赖细节 |
加上 迪米特法则(LOD),合称 OO 六大原则。
设计原则关系图:
开闭原则(终极目标)
↑
┌────┼────┬────┬────┐
│ │ │ │ │
LSP SRP DIP ISP LOD
(手段和保证)
2.2 开闭原则 OCP
软件实体应当对扩展开放,对修改关闭。
- 对扩展开放:有新需求时,可以添加新代码来实现新功能
- 对修改关闭:不应该修改已有的代码
实现关键 = 抽象
定义抽象层(接口/抽象类)──→ 稳定,不需修改
│
↓
具体实现类 ──→ 可扩展,满足"对扩展开放"
举例 — 计算器:
// ❌ 不符合OCP:每加一种运算就要改Calculator类
class Calculator {
public:
double calculate(double a, double b, string op) {
if (op == "+") return a + b;
if (op == "-") return a - b;
if (op == "*") return a * b;
if (op == "/") return a / b;
// 加取模?要改这里!违反开闭原则
}
};
// ✅ 符合OCP:定义抽象运算接口
class Operation {
public:
virtual double calculate(double a, double b) = 0;
virtual ~Operation() {}
};
class Add : public Operation {
public:
double calculate(double a, double b) override { return a + b; }
};
class Subtract : public Operation {
public:
double calculate(double a, double b) override { return a - b; }
};
// 新增加取模?只需新增类,不改旧代码
class Modulo : public Operation {
public:
double calculate(double a, double b) override {
return static_cast<int>(a) % static_cast<int>(b);
}
};
注意:即使无法百分之百做到开闭原则,但朝这个方向努力可以显著改善系统结构。仅对需求频繁变化的部分进行抽象,拒绝不成熟的抽象。
2.3 里氏替换原则 LSP
所有引用基类的地方必须能透明地使用其子类的对象。
两层含义:
- 子类必须完全实现父类的方法(重写)
- 子类可以有自己的个性(定义特有方法)
反面教材 — 经典的 Rectangle/Square 问题:
class Rectangle {
protected:
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int getWidth() { return width; }
int getHeight() { return height; }
int getArea() { return width * height; }
};
class Square : public Rectangle {
public:
Square(int side) : Rectangle(side, side) {}
void setWidth(int w) override { width = w; height = w; } // 正方形:设宽也设高
void setHeight(int h) override { width = h; height = h; } // 破坏了父类行为!
};
int main() {
Rectangle* rect = new Square(5);
rect->setWidth(10); // 期望宽=10,高不变=5
rect->setHeight(3); // 期望高=3,宽不变=10
cout << "宽度:" << rect->getWidth() << endl; // 实际: 3(被setHeight改了)
cout << "高度:" << rect->getHeight() << endl; // 实际: 3
cout << "面积:" << rect->getArea() << endl; // 实际: 9(期望: 30)
delete rect;
// Square 不能完美替代 Rectangle!违反 LSP
}
LSP 违反的后果:使用多态时,子类的行为不符合父类的契约,导致程序产生隐蔽 bug。
如果子类不能完整实现父类的方法,或父类的某些方法在子类中发生了"畸变",建议断开继承关系,采用依赖、聚合、组合等关系代替继承。
2.4 单一职责原则 SRP
应该有且仅有一个原因引起类的变更。
含义:一个类只负责一项职责。如果能想到多个原因去改变一个类,这个类就具有多个职责。
举例:
❌ 违反 SRP:
┌───────────────┐
│ Employee │ ← 职责太多
├───────────────┤
│ 计算工资() │ ← 财务职责
│ 考勤记录() │ ← HR职责
│ 技术考核() │ ← 技术职责
└───────────────┘
✅ 符合 SRP:
┌───────────────┐
│ Employee │ ← 只管基本信息
├───────────────┤
│ FinancialDept │──▶ 计算工资()
│ HRDept │──▶ 考勤记录()
└───────────────┘
// ❌ 违反SRP:一个类承担多个职责
class Employee {
public:
void calculateSalary() { /* 财务 */ }
void recordAttendance() { /* HR */ }
void evaluatePerformance() { /* 技术 */ }
};
// ✅ 符合SRP:拆分职责
class Employee { /* 基本信息 */ };
class FinancialDepartment { void calculateSalary(Employee&); };
class HRDepartment { void recordAttendance(Employee&); };
2.5 依赖倒置原则 DIP
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
核心 = 面向接口编程
❌ 依赖正置(面向实现编程):
高层模块 ──→ 低层模块A(具体实现)
低层模块B(具体实现)
换一个低层实现,高层就要改
✅ 依赖倒置(面向接口编程):
高层模块 ──→ 抽象接口 ←── 低层实现A
←── 低层实现B
换实现?高层不用改
// ❌ 违反DIP:高层直接依赖低层
class FrontEndDeveloper {
public:
void develop() { cout << "前端开发" << endl; }
};
class BackEndDeveloper {
public:
void develop() { cout << "后端开发" << endl; }
};
class Project {
FrontEndDeveloper front;
BackEndDeveloper back;
public:
void process() { front.develop(); back.develop(); }
// 加一个测试工程师?要改 Project 类!
};
// ✅ 符合DIP:都依赖抽象接口
class Developer { // 抽象接口
public:
virtual void develop() = 0;
virtual ~Developer() {}
};
class FrontEndDeveloper : public Developer {
public:
void develop() override { cout << "前端开发" << endl; }
};
class BackEndDeveloper : public Developer {
public:
void develop() override { cout << "后端开发" << endl; }
};
class Project {
vector<Developer*> team; // 依赖抽象
public:
void addDeveloper(Developer* d) { team.push_back(d); }
void process() {
for (auto d : team) d->develop();
}
};
生活类比:早期电脑所有硬件整合在一起,一个坏全部坏;现在电脑依赖插槽规范(接口),更换 CPU、内存方便。依赖倒置就是给软件"设计插槽"。
2.6 接口隔离原则 ISP
- 客户端不应该强行依赖它不需要的接口
- 类间的依赖关系应该建立在最小的接口上
核心 = 接口要细化,不要建立庞大臃肿的接口
// ❌ 违反ISP:一个胖接口
class IWorker {
public:
virtual void work() = 0;
virtual void eat() = 0;
virtual void sleep() = 0;
virtual void manage() = 0; // 只有管理者需要
};
// 普通员工被迫实现 manage(),但他不需要!
// ✅ 符合ISP:拆分为小接口
class IWorkable { public: virtual void work() = 0; };
class IEatable { public: virtual void eat() = 0; };
class IManagable { public: virtual void manage() = 0; };
class Employee : public IWorkable, public IEatable {
public:
void work() override { /* ... */ }
void eat() override { /* ... */ }
// 不需要实现 manage()!
};
class Manager : public IWorkable, public IEatable, public IManagable {
public:
void work() override { /* ... */ }
void eat() override { /* ... */ }
void manage() override { /* ... */ }
};
注意:接口不能太大(违反 ISP),也不能太小(导致接口泛滥)。粒度要合适。
2.7 迪米特法则 LOD
只与你直接的朋友通信(不跟陌生人说话)。
含义:一个对象应该对其他对象保持最少的了解。如果两个类不必直接通信,就不应发生直接的相互作用。需要调用时,可以通过第三者转发。
❌ 违反LOD:
Client ──直接访问──▶ GasEngine(陌生人)
✅ 符合LOD:
Client ──访问──▶ Car ──访问──▶ GasEngine
(只跟直接朋友 Car 说话)
生活中的迪米特法则:
- 买房找一个中介即可,不用认识所有卖房人
- 去医院看病找导医即可,不用自己知道所有科室位置
注意:过分使用迪米特法则会产生大量中介和传递类,导致系统过于复杂。要反复权衡,既做到结构清晰,又要高内聚低耦合。
外观模式和中介者模式是迪米特法则的典型应用。
三、设计模式三大分类
GoF(Gang of Four)23 种设计模式按目的分为三大类:
| 分类 | 关注点 | 包含模式 | 生活类比 |
|---|---|---|---|
| 创建型(5种) | 对象的创建机制,将创建与使用解耦 | 单例、工厂方法、抽象工厂、建造者、原型 | 角色创建攻略 |
| 结构型(7种) | 类与对象的组合,形成更大结构 | 适配器、桥接、装饰器、代理、外观、享元、组合 | 队伍搭配攻略 |
| 行为型(11种) | 对象之间的通信和职责分配 | 策略、观察者、模板方法、状态、命令、职责链、中介者、迭代器、备忘录、访问者、解释器 | 战斗策略攻略 |
口诀:创建造对象 · 结构搞组合 · 行为管交互
本课件详细讲解 4 种模式:简单工厂(创建型)、策略(行为型)、代理(结构型)、观察者(行为型)。
四、简单工厂模式
4.1 核心思想
将对象的创建与使用分离。客户端不直接 new 对象,而是向工厂"下订单"。
生活类比 — 餐厅点餐:
👤 客户端(顾客)──下订单──▶ 🏭 工厂(后厨)──创建──▶ 📦 产品(菜品)
应用场景:
- 图形绘制:根据类型创建不同图形对象(圆/矩形/三角)
- 数据库连接:根据数据库类型创建对应连接对象
- 文件解析:根据格式创建对应解析器
- 日志记录器:根据需求创建控制台/文件/数据库日志器
4.2 模式结构
┌──────────────┐ ┌──────────────────────┐
│ Client │────▶│ SimpleFactory │
└──────────────┘ │ +createProduct(type)│
└──────────┬───────────┘
│ creates
┌────────────┼───────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ConcreteProductA│ │ConcreteProductB│ │ ...更多产品 │
└──────┬───────┘ └──────┬───────────┘ └──────────────┘
└───────┬────────┘
▼
┌──────────────┐
│ Product │ ← 抽象产品类
│ +use() = 0 │
└──────────────┘
4.3 完整代码示例
#include <iostream>
using namespace std;
// ===== 抽象产品类 =====
class Product {
public:
virtual void use() = 0;
virtual ~Product() {}
};
// ===== 具体产品类A =====
class ConcreteProductA : public Product {
public:
void use() override {
cout << "使用产品A" << endl;
}
};
// ===== 具体产品类B =====
class ConcreteProductB : public Product {
public:
void use() override {
cout << "使用产品B" << endl;
}
};
// ===== 简单工厂类 =====
class SimpleFactory {
public:
static Product* createProduct(int type) {
switch (type) {
case 1: return new ConcreteProductA();
case 2: return new ConcreteProductB();
default: return nullptr;
}
}
};
// ===== 客户端 =====
int main() {
Product* productA = SimpleFactory::createProduct(1);
productA->use(); // 使用产品A
delete productA;
Product* productB = SimpleFactory::createProduct(2);
productB->use(); // 使用产品B
delete productB;
return 0;
}
4.4 三角色说明
| 角色 | 类名 | 职责 |
|---|---|---|
| 抽象产品 | Product |
定义产品的统一接口 |
| 具体产品 | ConcreteProductA/B |
实现抽象产品接口 |
| 工厂 | SimpleFactory |
根据参数创建对应产品实例 |
4.5 优缺点
| 优点 | 缺点 |
|---|---|
| 创建与使用分离,客户端不需要知道具体类名 | 工厂类职责过重,新增产品需修改工厂代码 |
| 集中管理创建逻辑 | 违反开闭原则(加新产品要改 switch) |
| 代码简洁可复用 | 工厂类出问题全系统瘫痪 |
4.6 拓展:工厂模式三兄弟
| 模式 | 特点 | 符合OCP? |
|---|---|---|
| 简单工厂 | 一个工厂方法 + switch 分支 | ❌ |
| 工厂方法 | 每种产品对应一个工厂类 | ✅ |
| 抽象工厂 | 创建一系列相关产品 | ✅ |
// 工厂方法模式:每个产品有自己的工厂
class ProductFactory {
public:
virtual Product* createProduct() = 0;
virtual ~ProductFactory() {}
};
class ProductAFactory : public ProductFactory {
public:
Product* createProduct() override { return new ConcreteProductA(); }
};
class ProductBFactory : public ProductFactory {
public:
Product* createProduct() override { return new ConcreteProductB(); }
};
// 新增产品C?只需新增 ProductC + ProductCFactory,不改旧代码!
4.7 智能指针改进版
#include <memory>
class SimpleFactory {
public:
static unique_ptr<Product> createProduct(int type) {
switch (type) {
case 1: return make_unique<ConcreteProductA>();
case 2: return make_unique<ConcreteProductB>();
default: return nullptr;
}
}
};
int main() {
auto productA = SimpleFactory::createProduct(1);
productA->use();
// 自动析构,不需要 delete!
}
五、策略模式
5.1 核心思想
定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。
解决痛点:消除代码中臃肿的 if/else 条件分支。
生活类比 — 智能洗衣机:
👕 洗衣机(Context)持有当前策略
├── ⚡ 标准洗策略
├── ⏱️ 快洗策略
└── 🧸 柔洗策略
按钮切换 = 运行时动态切换策略
应用场景:
- 支付方式:支付宝/微信/银行卡,运行时切换
- 排序算法:冒泡/快速/归并,灵活选择
- 图像处理:黑白/模糊/锐化滤镜
- 游戏角色攻击方式:近战/远程/魔法
5.2 模式结构
┌──────────────────────┐ ┌──────────────────┐
│ Context │─────▶│ Strategy │ ← 抽象策略
│ - strategy: Strategy*│ │ +execute() = 0 │
│ +setStrategy(s) │ └────────┬─────────┘
│ +executeStrategy() │ │
└──────────────────────┘ ┌─────────┴─────────┐
│ │
┌─────────┴──┐ ┌───────┴───┐
│StrategyA │ │StrategyB │
│+execute() │ │+execute() │
└─────────────┘ └───────────┘
5.3 完整代码示例
#include <iostream>
using namespace std;
// ===== 抽象策略类 =====
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
// ===== 具体策略类A =====
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
cout << "执行策略A" << endl;
}
};
// ===== 具体策略类B =====
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
cout << "执行策略B" << endl;
}
};
// ===== 上下文类 =====
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* strategy) : strategy(strategy) {}
void setStrategy(Strategy* strategy) {
this->strategy = strategy;
}
void executeStrategy() {
strategy->execute();
}
};
// ===== 客户端 =====
int main() {
ConcreteStrategyA strategyA;
ConcreteStrategyB strategyB;
Context context(&strategyA);
context.executeStrategy(); // 执行策略A
context.setStrategy(&strategyB); // 运行时切换策略
context.executeStrategy(); // 执行策略B
return 0;
}
5.4 三角色说明
| 角色 | 类名 | 职责 |
|---|---|---|
| 抽象策略 | Strategy |
定义算法的统一接口 |
| 具体策略 | ConcreteStrategyA/B |
实现具体算法 |
| 上下文 | Context |
持有策略引用,可在运行时切换 |
5.5 优缺点
| 优点 | 缺点 |
|---|---|
| 消除 if/else,代码清晰 | 策略类数量增加 |
| 运行时动态切换算法 | 客户端需了解各策略差异 |
| 符合开闭原则,新增策略不改旧代码 | |
| 算法可独立复用 |
5.6 拓展:策略模式 vs 简单工厂结合
// 策略 + 工厂:客户端只需传类型字符串,工厂自动创建策略
class StrategyFactory {
public:
static Strategy* createStrategy(const string& type) {
if (type == "A") return new ConcreteStrategyA();
if (type == "B") return new ConcreteStrategyB();
return nullptr;
}
};
class Context {
private:
unique_ptr<Strategy> strategy;
public:
Context(const string& type) {
strategy.reset(StrategyFactory::createStrategy(type));
}
void executeStrategy() { strategy->execute(); }
};
5.7 拓展:策略模式 vs 状态模式
| 特性 | 策略模式 | 状态模式 |
|---|---|---|
| 目的 | 选择算法 | 改变行为 |
| 切换方式 | 客户端主动切换 | 状态自动转换 |
| 策略/状态是否知道彼此 | 不知道 | 通常知道(可以转换) |
| 示例 | 选择支付方式 | 订单状态流转(待付→已付→已发) |
六、代理模式
6.1 核心思想
为其他对象提供一种代理以控制对这个对象的访问。
在不修改原有类的情况下,灵活地附加功能。
生活类比 — 代购中介:
👤 客户端 ──找代购──▶ 🤵 代理 ──采购──▶ 🏪 真实对象(海外正品店)
│
├── 验货(附加操作)
├── 清关(附加操作)
└── 发货(附加操作)
6.2 代理模式五种类型
| 类型 | 说明 | 典型场景 |
|---|---|---|
| 远程代理 | 为远程对象提供本地代理 | RPC、分布式系统 |
| 虚拟代理 | 延迟创建开销大的对象 | 懒加载、图片预加载 |
| 安全代理 | 控制访问权限 | 权限验证、身份认证 |
| 缓存代理 | 缓存结果提高性能 | Web缓存、数据库查询缓存 |
| 日志代理 | 记录方法调用信息 | 日志、性能监控、调试 |
6.3 模式结构
┌──────────────────────┐ ┌──────────────────┐
│ Client │────▶│ Subject │ ← 抽象主题
└──────────────────────┘ │ +request() = 0 │
└────────┬─────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌───────────────────┐ ┌─────────────────┐
│ Proxy │───持有──▶│ RealSubject │
│ - realSubject │ │ +request() │
│ +request() │ └─────────────────┘
│ {前置处理} │
│ {委派给realSubject}│
│ {后置处理} │
└───────────────────┘
6.4 完整代码示例
#include <iostream>
using namespace std;
// ===== 抽象主题类 =====
class Subject {
public:
virtual void request() = 0;
virtual ~Subject() {}
};
// ===== 真实主题类 =====
class RealSubject : public Subject {
public:
void request() override {
cout << "RealSubject处理请求" << endl;
}
};
// ===== 代理类 =====
class Proxy : public Subject {
private:
RealSubject* realSubject;
public:
Proxy() : realSubject(nullptr) {}
~Proxy() {
delete realSubject;
}
void request() override {
// 前置处理:权限验证、日志记录等
cout << "[Proxy] 前置处理:权限验证..." << endl;
// 懒加载:第一次调用时才创建真实对象(虚拟代理)
if (realSubject == nullptr) {
cout << "[Proxy] 延迟创建 RealSubject..." << endl;
realSubject = new RealSubject();
}
// 委派给真实对象
realSubject->request();
// 后置处理:日志记录、缓存等
cout << "[Proxy] 后置处理:记录日志..." << endl;
}
};
// ===== 客户端 =====
int main() {
Proxy proxy;
proxy.request();
// 输出:
// [Proxy] 前置处理:权限验证...
// [Proxy] 延迟创建 RealSubject...
// RealSubject处理请求
// [Proxy] 后置处理:记录日志...
proxy.request(); // 第二次调用,不再创建
// 输出:
// [Proxy] 前置处理:权限验证...
// RealSubject处理请求
// [Proxy] 后置处理:记录日志...
return 0;
}
6.5 三角色说明
| 角色 | 类名 | 职责 |
|---|---|---|
| 抽象主题 | Subject |
定义 request() 接口 |
| 真实主题 | RealSubject |
实现实际业务逻辑 |
| 代理 | Proxy |
控制访问,附加功能,持有真实主题引用 |
6.6 优缺点
| 优点 | 缺点 |
|---|---|
| 不修改真实类即可增强功能 | 增加了一层间接,可能降低效率 |
| 符合开闭原则 | 代理类可能变得复杂 |
| 可实现懒加载、权限控制、缓存等 | |
| 符合单一职责(真实类只管业务,代理管附加) |
6.7 拓展:代理模式 vs 装饰器模式
| 特性 | 代理模式 | 装饰器模式 |
|---|---|---|
| 目的 | 控制访问 | 增强功能 |
| 关注点 | 代理对象是否可被访问 | 给对象叠加功能 |
| 创建对象 | 代理创建/控制真实对象 | 装饰器接受已有对象 |
| 示例 | 权限代理、虚拟代理 | 给窗口加滚动条、边框 |
七、观察者模式
7.1 核心思想
定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。
别名:发布-订阅模式(Publish-Subscribe)、模型-视图模式(Model-View)、源-监听器模式(Source-Listener)
生活类比:
📰 主题/发布者(Subject)
├── 观察者A(订户A)
├── 观察者B(订户B)
└── 观察者C(订户C)
主题状态变化 → notify() → 所有观察者自动更新
应用场景:
- 订阅者模式:发布者维护订阅者列表,状态变化时自动通知
- 消息通知系统:用户订阅不同类型消息
- MVC模式:Model 通知 View 更新
- 股票市场:投资者订阅股票价格变动
- 游戏事件系统:玩家操作、敌人生成等事件触发通知
7.2 模式结构
┌─────────────────────────────┐ ┌─────────────────────┐
│ Subject │ │ Observer │
│ - observers: vector<Observer*>│───▶│ +update() = 0 │
│ +attach(observer) │ └──────────┬──────────┘
│ +detach(observer) │ │
│ +notify() │ ┌──────────┴──────────┐
└─────────────┬───────────────┘ │ │
│ ┌─────┴────┐ ┌──────┴─────┐
│ │ConcreteObs│ │ConcreteObs │
│ │ A │ │ B │
▼ │+update() │ │+update() │
┌─────────────────┐ └───────────┘ └────────────┘
│ ConcreteSubject │
│ - state │
│ +getState() │
│ +setState() │──→ 状态变化时调用 notify()
└─────────────────┘
7.3 完整代码示例
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// ===== 抽象观察者类 =====
class Observer {
public:
virtual void update() = 0;
virtual ~Observer() {}
};
// ===== 具体观察者类 =====
class ConcreteObserver : public Observer {
private:
string name;
public:
ConcreteObserver(const string& n) : name(n) {}
void update() override {
cout << name << ": 收到通知,进行更新操作" << endl;
}
};
// ===== 抽象主题类 =====
class Subject {
private:
vector<Observer*> observers;
public:
void attach(Observer* observer) {
observers.push_back(observer);
}
void detach(Observer* observer) {
for (auto it = observers.begin(); it != observers.end(); ++it) {
if (*it == observer) {
observers.erase(it);
break;
}
}
}
void notify() {
for (auto observer : observers) {
observer->update();
}
}
};
// ===== 客户端 =====
int main() {
Subject subject;
ConcreteObserver observer1("观察者1");
ConcreteObserver observer2("观察者2");
subject.attach(&observer1);
subject.attach(&observer2);
subject.notify(); // 两个观察者都收到通知
subject.detach(&observer1);
subject.notify(); // 只有观察者2收到通知
return 0;
}
输出:
观察者1: 收到通知,进行更新操作
观察者2: 收到通知,进行更新操作
观察者2: 收到通知,进行更新操作
7.4 两角色说明
| 角色 | 类名 | 职责 |
|---|---|---|
| 抽象主题 | Subject |
维护观察者列表,提供 attach/detach/notify |
| 抽象观察者 | Observer |
定义 update 接口,收到通知后执行更新 |
7.5 优缺点
| 优点 | 缺点 |
|---|---|
| 观察者与主题解耦(松耦合) | 观察者多时通知耗时 |
| 运行时动态增删观察者 | 循环依赖可能导致死锁 |
| 符合开闭原则 | 主题不知道观察者具体做了什么 |
7.6 拓展:带数据推送的观察者模式
// 推模式:主题将数据主动推给观察者
class Observer {
public:
virtual void update(const string& data) = 0;
virtual ~Observer() {}
};
class Subject {
private:
vector<Observer*> observers;
string state;
public:
void setState(const string& newState) {
state = newState;
notify(); // 状态变化自动通知
}
void notify() {
for (auto o : observers) {
o->update(state); // 推送数据
}
}
};
// 拉模式:观察者主动从主题拉取数据
class Observer2 {
public:
virtual void update(Subject* subject) = 0;
virtual ~Observer2() {}
};
推模式 vs 拉模式:
| 模式 | 数据传递 | 优点 | 缺点 |
|---|---|---|---|
| 推模式 | 主题主动推送数据 | 观察者无需知道主题细节 | 可能推送观察者不需要的数据 |
| 拉模式 | 观察者主动拉取 | 观察者按需获取 | 观察者需依赖主题接口 |
7.7 拓展:观察者模式在 C++ 标准库中的应用
C++ 的 std::function + 回调函数是一种轻量级的观察者模式实现:
#include <functional>
#include <vector>
#include <string>
class EventEmitter {
std::vector<std::function<void(const std::string&)>> callbacks;
public:
void on(std::function<void(const std::string&)> cb) {
callbacks.push_back(cb);
}
void emit(const std::string& event) {
for (auto& cb : callbacks) {
cb(event);
}
}
};
int main() {
EventEmitter emitter;
emitter.on([](const std::string& e) {
cout << "监听器1收到: " << e << endl;
});
emitter.on([](const std::string& e) {
cout << "监听器2收到: " << e << endl;
});
emitter.emit("按钮点击");
return 0;
}
八、设计模式综合对比与速查表
8.1 四种模式核心对比
| 维度 | 简单工厂 | 策略模式 | 代理模式 | 观察者模式 |
|---|---|---|---|---|
| 分类 | 创建型 | 行为型 | 结构型 | 行为型 |
| 核心 | 创建与使用分离 | 算法封装可替换 | 控制对象访问 | 一对多通知 |
| 解决的问题 | new 对象耦合 | if/else 分支 | 直接访问控制 | 状态同步 |
| 关键角色 | 工厂类 | 上下文类 | 代理类 | 主题+观察者 |
| 符合 OCP | ❌ | ✅ | ✅ | ✅ |
| 生活类比 | 后厨做菜 | 洗衣机模式切换 | 代购中介 | 报纸订阅 |
8.2 四种模式的参与者对比
| 参与者 | 简单工厂 | 策略模式 | 代理模式 | 观察者模式 |
|---|---|---|---|---|
| 抽象 | Product | Strategy | Subject | Observer |
| 具体实现 | ConcreteProductA/B | ConcreteStrategyA/B | RealSubject | ConcreteObserver |
| 管理/协调 | SimpleFactory | Context | Proxy | Subject(主题) |
| 客户端 | Client | Client | Client | Client |
8.3 各模式体现的设计原则
| 原则 | 简单工厂 | 策略 | 代理 | 观察者 |
|---|---|---|---|---|
| OCP 开闭原则 | ❌ | ✅ | ✅ | ✅ |
| SRP 单一职责 | ✅ | ✅ | ✅ | ✅ |
| DIP 依赖倒置 | ✅ | ✅ | ✅ | ✅ |
| LOD 迪米特 | ✅ | — | ✅ | ✅ |
| LSP 里氏替换 | ✅ | ✅ | ✅ | ✅ |
8.4 GoF 23 种模式速查表
创建型(5种)— 关注"怎么创建"
| 模式 | 意图 | 一句话 |
|---|---|---|
| 单例 Singleton | 确保只有一个实例 | 全局唯一 |
| 工厂方法 Factory Method | 子类决定创建哪个产品 | 一产品一工厂 |
| 抽象工厂 Abstract Factory | 创建一系列相关产品 | 产品族工厂 |
| 建造者 Builder | 分步构建复杂对象 | 流水线组装 |
| 原型 Prototype | 克隆已有对象 | 复制粘贴 |
结构型(7种)— 关注"怎么组合"
| 模式 | 意图 | 一句话 |
|---|---|---|
| 适配器 Adapter | 接口转换 | 转接头 |
| 桥接 Bridge | 分离抽象与实现 | 两维扩展 |
| 装饰器 Decorator | 动态添加功能 | 套娃增强 |
| 代理 Proxy | 控制访问 | 中介 |
| 外观 Facade | 简化复杂接口 | 一键入口 |
| 享元 Flyweight | 共享细粒度对象 | 对象池 |
| 组合 Composite | 树形结构统一处理 | 文件夹套文件夹 |
行为型(11种)— 关注"怎么交互"
| 模式 | 意图 | 一句话 |
|---|---|---|
| 策略 Strategy | 封装可替换算法 | 按钮切换 |
| 观察者 Observer | 一对多通知 | 发布订阅 |
| 模板方法 Template | 算法骨架,子类填充 | 填空题 |
| 状态 State | 状态驱动行为 | 状态机 |
| 命令 Command | 封装请求为对象 | 撤销重做 |
| 职责链 Chain | 请求链式传递 | 踢皮球 |
| 中介者 Mediator | 集中管理对象交互 | 聊天室 |
| 迭代器 Iterator | 顺序访问集合 | 遥控翻页 |
| 备忘录 Memento | 保存恢复状态 | 存档读档 |
| 访问者 Visitor | 分离操作与结构 | 新增操作不改类 |
| 解释器 Interpreter | 定义语言语法 | 小型解析器 |
8.5 设计原则速查
| 原则 | 核心 | 口诀 |
|---|---|---|
| 开闭 OCP | 扩展开放,修改关闭 | 加新的不改旧的 |
| 里氏 LSP | 子类可替换父类 | 子类别搞特殊 |
| 单一 SRP | 一个类一个职责 | 专人专事 |
| 倒置 DIP | 依赖抽象不依赖具体 | 面向接口编程 |
| 隔离 ISP | 接口要细化 | 别做胖接口 |
| 迪米特 LOD | 只跟朋友说话 | 别跟陌生人讲话 |
8.6 模式选择决策图
遇到什么问题?
│
├── 对象创建太复杂/耦合?
│ ├── 创建单个产品 → 简单工厂
│ ├── 创建多种产品 → 工厂方法 / 抽象工厂
│ ├── 分步构建复杂对象 → 建造者
│ ├── 只需一个实例 → 单例
│ └── 克隆已有对象 → 原型
│
├── 需要切换算法/行为?
│ ├── 运行时切换算法 → 策略模式
│ ├── 状态决定行为 → 状态模式
│ ├── 算法骨架固定 → 模板方法
│ └── 封装请求 → 命令模式
│
├── 需要控制访问/增强功能?
│ ├── 控制对象访问 → 代理模式
│ ├── 动态叠加功能 → 装饰器模式
│ ├── 接口不兼容 → 适配器模式
│ └── 简化复杂子系统 → 外观模式
│
└── 需要对象间通信?
├── 一对多通知 → 观察者模式
├── 多对象交互 → 中介者模式
└── 请求链式处理 → 职责链模式
8.7 4 种模式 UML 类图速览
简单工厂:
Factory ──create──▶ Product(abstract)
▲
┌─────┼─────┐
A B ...
策略模式:
Context ──持有──▶ Strategy(abstract)
▲
┌─────┼─────┐
A B ...
代理模式:
Client ──▶ Subject(abstract)
▲
┌──────┴──────┐
Proxy RealSubject
│──持有──▶│
观察者:
Subject ──notify──▶ Observer(abstract)
│ ▲
│ ┌─────┼─────┐
│ A B ...
└─attach/detach─▶ Observer列表

浙公网安备 33010602011771号