搜索

C++ 教程

C++基础

第1章 搭建开发环境与第一个程序 第2章 基本数据类型与变量 第3章 运算符与表达式 第4章 流程控制语句 第5章 数组与字符串 第6章 函数基础 第7章 指针基础 第8章 引用与const限定 第9章 动态内存管理 第10章 结构体与联合体 第11章 函数进阶

面向对象

第12章 面向对象编程入门 第13章 封装与访问控制 第14章 运算符重载 第15章 继承与派生 第16章 多态与虚函数

STL与进阶

第17章 模板编程基础 第18章 标准模板库(STL)容器 第19章 STL算法与迭代器 第20章 异常处理机制 第21章 文件输入输出 第22章 字符串类string 第23章 命名空间与作用域 第24章 类型转换操作符

现代C++

第25章 智能指针 第26章 移动语义与右值引用 第27章 Lambda表达式 第28章 多线程编程基础 第29章 模板元编程入门 第30章 设计模式基础 第31章 C++11/14新特性 第32章 C++17/20核心特性

实战进阶

第33章 性能优化技巧 第34章 调试与测试 第35章 项目结构与构建 第36章 网络编程入门 第37章 数据库连接操作 第38章 图形界面开发基础 第39章 综合项目实战 第40章 进阶学习路线

第30章 设计模式基础

第30章 设计模式基础

高级 👤 已掌握面向对象核心,希望提升代码架构能力的开发者 🔧 构建可复用、易维护、高内聚低耦合的中大型软件系统
本章你将学到
  • 设计模式的核心思想与分类
  • 三种最常用创建型模式的实现与应用场景
  • 如何识别代码中的“坏味道”并应用模式重构
核心概念工厂模式单例模式观察者模式

调试日志里频繁出现“内存泄漏”,新加一个功能要修改十几个文件,团队成员抱怨代码像“意大利面条”一样难以理解——如果你遇到过这些问题,说明你的项目架构需要设计模式了。设计模式不是语法,而是针对特定设计问题的、可复用的优秀解决方案模板。

生活类比: 就像乐高积木的通用连接件,不同形状的积木(类)可以通过标准接口(模式)牢固地组合在一起,构建出复杂稳定的结构。技术定义: 设计模式是软件设计中,针对一类反复出现的问题,所总结出的、经过验证的通用解决方案。它描述了对象或类之间的关系,以及它们如何协作完成任务。

设计模式的“家族”与核心价值

GoF(四人帮)的经典著作《设计模式:可复用面向对象软件的基础》归纳了23种模式,分为三大类。理解这个分类,能帮你快速定位问题该用哪个模式解决。

创建型模式
↘
结构型模式
↘
行为型模式
↓
对象创建
→
类/对象组合
→
对象间职责分配

实际开发中,大部分团队会优先掌握其中5-8个最常用的模式。设计模式的核心价值在于:提升代码复用性、增强系统可扩展性、降低模块间耦合度、提高代码可读性。 我在一个图像处理项目中,通过引入工厂模式,将支持新图片格式的开发时间从2天缩短到2小时。

创建型模式:解开对象创建的枷锁

当你的代码中充斥着大量的 new 关键字,并且创建逻辑复杂多变时,创建型模式就该登场了。它封装了对象的创建过程,使系统独立于具体对象的创建、组合和表示。

模式名称要解决的问题典型应用场景C++实现关键
单例模式 (Singleton)确保一个类只有一个实例,并提供全局访问点配置管理器、日志记录器、线程池、数据库连接池私有构造函数、静态成员函数、线程安全(C++11后可用局部静态变量)
工厂方法 (Factory Method)将对象创建延迟到子类,避免客户端代码依赖具体类文档编辑器创建不同类型的文档(Word, PDF);跨平台UI组件创建定义创建对象的接口,子类决定实例化哪个类
抽象工厂 (Abstract Factory)创建一系列相关或依赖的对象,而不指定其具体类构建不同风格(如Modern/Vintage)的整套UI控件;连接不同数据库(MySQL/Oracle)的整套DAO对象包含多个工厂方法的接口,每个具体工厂生产一套产品

单例模式:全局唯一的“管家”

日志系统是单例模式的经典场景。如果每个模块都自己创建日志对象,不仅浪费资源,还可能导致日志文件写入冲突。

// C++11 线程安全的Meyer‘s Singleton实现(推荐)
class Logger {
public:
    static Logger& getInstance() {
        static Logger instance; // C++11保证局部静态变量初始化线程安全
        return instance;
    }
    void log(const std::string& message) {
        // 写入日志文件...
        std::cout << "[LOG] " << message << std::endl;
    }
    // 禁止拷贝和赋值
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
private:
    Logger() {} // 私有构造函数
    ~Logger() {}
};

// 使用方式
Logger::getInstance().log("Application started.");

常见误区

  • 滥用单例: 单例本质上是“全局变量”,滥用会导致代码高度耦合,难以测试。只有真正需要全局唯一性的“管理器”类才适合。
  • 线程安全陷阱: 在C++11之前,使用“双检锁”实现单例很容易写错,导致未定义行为。C++11之后,请统一使用上面展示的局部静态变量方式。
  • 忽视销毁: 单例对象在程序结束时析构,如果析构顺序依赖其他全局/静态对象,可能引发问题。设计时要考虑资源清理的顺序。

工厂方法模式:生产线的标准化

假设你正在开发一个支持多种传感器(如温度传感器DHT22、湿度传感器SHT31)的数据采集框架。每次新增传感器类型,都要修改核心采集逻辑,这违反了“开闭原则”。

客户端代码
→
调用 SensorFactory::createSensor(type)
→
工厂根据type返回 DHT22 或 SHT31 对象
→
客户端统一调用 sensor->readData()
// 产品接口
class Sensor {
public:
    virtual ~Sensor() = default;
    virtual float readData() = 0;
};

// 具体产品
class DHT22Sensor : public Sensor { public: float readData() override { /* 读取温度 */ return 25.3; } };
class SHT31Sensor : public Sensor { public: float readData() override { /* 读取湿度 */ return 60.5; } };

// 简单工厂(非严格GoF工厂方法,但更常用)
class SensorFactory {
public:
    static std::unique_ptr createSensor(const std::string& type) {
        if (type == "DHT22") return std::make_unique();
        else if (type == "SHT31") return std::make_unique();
        // 新增传感器只需修改这里
        return nullptr;
    }
};

如果你看到代码里有一长串的 if-else 或 switch-case 来创建对象,这就是引入工厂模式的强烈信号。我在一个通信协议解析项目中踩过这个坑,最初用条件判断创建不同协议解析器,后来协议增加到十几种,代码变得难以维护,改用工厂模式配合注册表后清爽多了。

编者提示: 工厂模式中返回的通常是动态分配的对象。请务必结合第25章《智能指针》的内容,使用 std::unique_ptr 来管理这些对象,避免内存泄漏,并明确所有权语义。

结构型模式:搭建灵活的类关系

结构型模式关注如何将类或对象组合成更大、更复杂的结构,同时保持结构的灵活和高效。这里我们重点看一个极其常用的模式——适配器模式。

生活类比: 就像电源转换插头,让你英标的笔记本电脑插头(客户端)能在欧标的墙插(已有接口)上正常工作。技术定义: 适配器模式将一个类的接口转换成客户期望的另一个接口,使原本因接口不兼容而无法一起工作的类可以协同工作。

假设你项目里有一个遗留的、接口陈旧的第三方日志库 OldLogger,它只有一个 writeToConsole 方法。但你的新系统统一使用一个接收 std::string 参数的 log 接口。

// 已有的、不兼容的类
class OldLogger {
public:
    void writeToConsole(const char* msg, int length) {
        std::cout.write(msg, length);
    }
};

// 目标接口(你的新系统标准)
class NewLoggerInterface {
public:
    virtual ~NewLoggerInterface() = default;
    virtual void log(const std::string& message) = 0;
};

// 适配器类:继承目标接口,内部包含一个OldLogger对象
class LoggerAdapter : public NewLoggerInterface {
private:
    OldLogger oldLogger;
public:
    void log(const std::string& message) override {
        // 适配过程:将string接口转换为char*和length
        oldLogger.writeToConsole(message.c_str(), message.size());
    }
};

// 客户端代码可以统一使用NewLoggerInterface接口,无需知道OldLogger的存在
std::unique_ptr logger = std::make_unique();
logger->log("Hello, Adapter Pattern!");

行为型模式:对象间的智慧协作

行为型模式负责对象间的高效通信和职责分配。观察者模式是其中使用频率最高的模式之一,它定义了对象间的一种一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。

GUI事件处理

  • 按钮(被观察者)被点击
  • 多个对话框、状态栏(观察者)自动更新

数据模型变更

  • 股票价格(被观察者)变化
  • 多个图表视图(观察者)实时重绘

游戏引擎

  • 玩家生命值(被观察者)减少
  • UI血条、音效系统、成就系统(观察者)触发响应

下面是一个简化的C++实现框架,注意使用弱引用来避免观察者对象生命周期管理带来的问题。

#include 
#include 
#include 

// 前向声明
class Observer;

// 被观察者(主题)
class Subject {
    std::vector> observers_; // 使用weak_ptr避免循环引用
    std::string state_;
public:
    void attach(std::weak_ptr obs) { observers_.push_back(obs); }
    void setState(const std::string& newState) {
        state_ = newState;
        notify();
    }
    void notify(); // 通知所有观察者
    std::string getState() const { return state_; }
};

// 观察者接口
class Observer : public std::enable_shared_from_this {
public:
    virtual ~Observer() = default;
    virtual void update(Subject* subject) = 0;
};

void Subject::notify() {
    for (auto& weakObs : observers_) {
        if (auto obs = weakObs.lock()) { // 检查观察者是否还存在
            obs->update(this);
        }
    }
}

// 具体观察者
class ConcreteObserver : public Observer {
    std::string name_;
public:
    ConcreteObserver(const std::string& name) : name_(name) {}
    void update(Subject* subject) override {
        std::cout << name_ << " received update. New state: " << subject->getState() << std::endl;
    }
};

关键点:何时使用设计模式? 不要为了用模式而用模式。当你发现代码有“坏味道”,如:重复代码太多、类职责过重、修改一处牵动全身、难以添加新功能时,再回头查阅设计模式手册,寻找对应的解决方案。模式是“重构”的目标,而不是初始设计的起点。

动手试一试

  1. 改造日志系统: 你有一个简单的控制台日志类 ConsoleLogger。请为其实现一个单例模式,并添加一个静态方法 setLogLevel(Level) 来全局控制日志输出级别(如DEBUG, INFO, ERROR)。
  2. 实现一个图形工厂: 定义一个 Shape 抽象类,包含 draw() 和 area() 纯虚函数。创建 Circle 和 Rectangle 两个派生类。最后编写一个 ShapeFactory,根据传入的字符串 “Circle” 或 “Rectangle” 返回对应的形状对象。
  3. 模拟简单的事件通知: 创建一个 WeatherStation(被观察者)类,它有一个温度数据成员。创建两个观察者:Display(在温度变化时打印信息)和 Logger(在温度超过30度时记录警告)。实现观察者模式,当气象站更新温度时,两个观察者能自动响应。

检验你的理解

  • 单例模式的构造函数为什么必须是私有的?如果公开会有什么后果?
  • 工厂方法模式和抽象工厂模式最主要的区别是什么?请各举一个适合使用该模式的例子。
  • 在观察者模式的实现中,为什么存储观察者时推荐使用 std::weak_ptr 而不是 std::shared_ptr?
  • 假设你有一个已有的、功能完善的类,但其接口不符合新系统的要求。在不修改原有类代码的前提下,你可以使用哪种设计模式来让它融入新系统?

本章小结

  • 设计模式是针对常见软件设计问题的、可复用的解决方案模板,分为创建型、结构型和行为型三大类。
  • 单例模式确保一个类仅有一个实例,C++11后推荐使用局部静态变量实现线程安全版本。
  • 工厂模式(工厂方法/抽象工厂)将对象创建逻辑封装起来,使客户端代码与具体类解耦,极大提升了系统的可扩展性。
  • 适配器模式像转换插头一样,使接口不兼容的类能够一起工作,常用于集成遗留代码或第三方库。
  • 观察者模式实现了一对多的依赖关系,是事件驱动系统、MVC架构的核心,实现时需注意使用弱引用来管理观察者生命周期。
  • 学习设计模式的正确姿势是:先识别代码中的“坏味道”,再寻找对应的模式进行重构,而非在项目初期生搬硬套。

意见反馈

微信二维码 微信扫码关注 抖音二维码 抖音扫码关注