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

所有引用基类的地方必须能透明地使用其子类的对象。

两层含义

  1. 子类必须完全实现父类的方法(重写)
  2. 子类可以有自己的个性(定义特有方法)

反面教材 — 经典的 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列表
posted @ 2026-08-03 13:07  小怪兽zzy  阅读(1)  评论(0)    收藏  举报