【C++/Qt 进阶】设计模式系列(6):桥接模式——把“两个独立变化的维度“拆开

系列文章:

  1. 单例模式:【C++/Qt 进阶】设计模式系列(1):单例模式------饿汉式内存泄漏真相、指针单例存在的意义、五种实现终极对比-CSDN博客
  2. 工厂模式与抽象工厂模式:【C++/Qt 进阶】设计模式系列(2):工厂模式------从游戏角色到披萨店-CSDN博客
  3. 建造者模式:【C++/Qt 进阶】设计模式系列(3): 建造者模式------把"复杂对象的组装"从业务代码里剥离出来-CSDN博客
  4. 原型模式:【C++/Qt 进阶】设计模式系列(4):原型模式------用"克隆"代替"重新构造"-CSDN博客
  5. 适配器模式:【C++/Qt 进阶】设计模式系列(5):适配器模式------给"不兼容的接口"贴一层翻译膜-CSDN博客
  6. 桥接模式(本篇)
  7. 过滤器模式(持续更新中)

写在前面

⚠️ 上一篇适配器是"结构型 "模式开篇,本篇是"结构型"第二站。两者常被混淆,本篇将专门对比,厘清差异。

上一讲我们通过适配器模式解决了"老接口转换为新接口"的问题。但还有一类场景,适配器难以优雅应对------

"我的类有两个独立变化的维度,若用继承强行组合,会导致类数量呈指数级爆炸。"

这在 Qt 项目中极为常见,比如画图工具中的"形状 × 颜色"组合:

cpp 复制代码
// 反人类的继承写法
class RedCircle    : public Circle { void draw() override { /* 红圆 */ } };
class GreenCircle  : public Circle { void draw() override { /* 绿圆 */ } };
class RedSquare    : public Square { void draw() override { /* 红方 */ } };
class GreenSquare  : public Square { void draw() override { /* 绿方 */ } };

// 4 种形状 × 5 种颜色 = 20 个类!再加一种颜色 → 25 个!

当 N 个形状 × M 种颜色时,需创建 N×M 个具体类。而形状和颜色都可能持续扩展------这就是典型的类爆炸。

菜鸟教程定义:将抽象部分与实现部分分离,使它们可以独立地变化。 (参考)
记忆口诀:"两条独立的线" → 桥接;"一对接口" → 适配。


一、30 秒看懂桥接模式

维度 适配器模式(贴翻译膜) 桥接模式(拆两条线)
解决什么 老接口 → 新接口 两个独立维度的类爆炸
关心什么 翻译、兼容 让两个维度各自自由扩展
何时用 接入老系统 / 第三方 SDK 类数量呈 N×M 关系时
典型应用 日志封装 / 协议转换 画图 / 跨平台 / 设备驱动
设计时机 系统已存在后 系统设计时就使用

一句话总结:用"组合(has-a)"替代"继承(is-a)",把原本"竖着继承"的结构改为"横着组合",实现两个维度的解耦与独立演化。 在 C++ 中,即通过抽象类持有一个实现接口的指针或引用。


二、UML 类图与角色

以下为纯 C++/Qt 视角 的桥接模式结构图,采用 Qt 容器 + std::unique_ptr + 虚函数声明的写法。

text 复制代码
            ┌──────────────────────────────┐
            │  class IShape                │   ← 抽象(Abstraction)
            │  {                            │   客户直接调用的接口
            │  protected:                  │
            │    std::unique_ptr            │
            │      <IDrawAPI> m_drawer;     │   ← 桥:持有实现接口
            │  public:                      │
            │    explicit IShape(           │
            │      std::unique_ptr          │
            │        <IDrawAPI> d)          │
            │      : m_drawer(std::move(d)){}
            │    virtual void draw() = 0;  │
            │  };                           │
            └───────────────┬──────────────┘
                            │  继承
                ┌───────────┴────────────┐
                ▼                        ▼
            ┌────────────────┐    ┌────────────────┐
            │  class Circle  │    │  class Square  │   ← 扩展抽象
            │   : public     │    │   : public     │   (Refined
            │   IShape       │    │   IShape       │    Abstraction)
            │  { void draw() │    │  { void draw() │
            │   override; }  │    │   override; }  │
            └────────────────┘    └────────────────┘
                            │
                            │  桥接(组合,非继承)
                            ▼
            ┌──────────────────────────────┐
            │  class IDrawAPI              │   ← 实现接口(Implementor)
            │  {                            │
            │  public:                      │
            │    virtual ~IDrawAPI() = default;
            │    virtual void drawCircle(   │
            │      int r, int x, int y) = 0;│
            │    virtual void drawSquare(   │
            │      int side, int x, int y) = 0;│
            │  };                           │
            └───────────────┬──────────────┘
                            │  实现
                ┌───────────┴────────────┐
                ▼                        ▼
            ┌──────────────┐      ┌──────────────┐
            │ class RedDraw │      │ class GreenDraw │   ← 具体实现
            │  : public     │      │  : public     │   (Concrete
            │  IDrawAPI     │      │  IDrawAPI     │    Implementor)
            │ { drawCircle │      │ { ... }       │
            │   override{} │      │               │
            └──────────────┘      └──────────────┘

四大核心角色:

角色 职责 C++/Qt 中的对应实现
抽象(Abstraction) 客户端直接使用的接口,持有实现接口的指针 class IShape { std::unique_ptr<IDrawAPI> m_drawer; }
扩展抽象(Refined Abstraction) 抽象的具体化(如圆、方、三角等) Circle : public IShape
实现接口(Implementor) 为"实现维度"提供统一抽象(如颜色、平台、驱动等) class IDrawAPI { virtual drawCircle() = 0; }
具体实现(Concrete Implementor) 实现接口的具体类 RedDraw : public IDrawAPI

💡 C++ 特色实践 :桥接模式在 C++ 中常结合 pImpl(Pointer to Implementation)惯用法 使用------将对外接口与内部实现彻底隔离,通过 std::unique_ptr<Impl> 实现零成本抽象。本篇第 5 节将深入剖析这一高阶技巧。


三、基础代码实现(C++/Qt 版)

以 Qt 工程师最熟悉的"跨平台日志输出"为例,演示如何将"日志格式器"与"日志目的地"两个维度解耦。

3.1 实现接口:日志目的地

cpp 复制代码
// ilogdestination.h
#include <QString>

class ILogDestination {
public:
    virtual ~ILogDestination() = default;
    virtual void write(const QString& line) = 0;
    virtual void flush() = 0;
};

3.2 具体实现:三种日志目的地

cpp 复制代码
// console_destination.h
#ifndef CONSOLE_DESTINATION_H
#define CONSOLE_DESTINATION_H

#include "ilogdestination.h"
#include <iostream>

class ConsoleDestination : public ILogDestination {
public:
    void write(const QString& line) override {
        std::cout << line.toStdString() << std::endl;
    }

    void flush() override {
        std::cout.flush();
    }
};

#endif // CONSOLE_DESTINATION_H
cpp 复制代码
// file_destination.h
#ifndef FILE_DESTINATION_H
#define FILE_DESTINATION_H

#include "ilogdestination.h"
#include <QFile>
#include <QTextStream>

class FileDestination : public ILogDestination {
private:
    QFile m_file;
    QTextStream m_stream;

public:
    explicit FileDestination(const QString& path)
        : m_file(path), m_stream(&m_file) {
        if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text))
            throw std::runtime_error("Failed to open log file");
    }

    void write(const QString& line) override {
        m_stream << line << '\n';
    }

    void flush() override {
        m_stream.flush();
    }

    ~FileDestination() override {
        m_file.close();
    }
};

#endif // FILE_DESTINATION_H
cpp 复制代码
// debug_output_destination.h
#ifndef DEBUG_OUTPUT_DESTINATION_H
#define DEBUG_OUTPUT_DESTINATION_H

#include "ilogdestination.h"
#include <QDebug>

class DebugOutputDestination : public ILogDestination {
public:
    void write(const QString& line) override {
        qInfo() << line;
    }

    void flush() override {
        qFlush();
    }
};

#endif // DEBUG_OUTPUT_DESTINATION_H

3.3 抽象层:日志格式器(桥接主干)

cpp 复制代码
// ilogformatter.h
#include <QString>

class ILogFormatter {
public:
    virtual ~ILogFormatter() = default;
    virtual QString format(const QString& message) = 0;
};
cpp 复制代码
// simple_formatter.h
#ifndef SIMPLE_FORMATTER_H
#define SIMPLE_FORMATTER_H

#include "ilogformatter.h"

class SimpleFormatter : public ILogFormatter {
public:
    QString format(const QString& message) override {
        return message;
    }
};

#endif // SIMPLE_FORMATTER_H
cpp 复制代码
// timestamp_formatter.h
#ifndef TIMESTAMP_FORMATTER_H
#define TIMESTAMP_FORMATTER_H

#include "ilogformatter.h"
#include <QDateTime>

class TimestampFormatter : public ILogFormatter {
public:
    QString format(const QString& message) override {
        auto now = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        return "[" + now + "] " + message;
    }
};

#endif // TIMESTAMP_FORMATTER_H

3.4 桥接主类:日志记录器

cpp 复制代码
// logger.h
#ifndef LOGGER_H
#define LOGGER_H

#include "ilogdestination.h"
#include "ilogformatter.h"
#include <memory>

class Logger {
private:
    std::unique_ptr<ILogFormatter> m_formatter;
    std::unique_ptr<ILogDestination> m_destination;

public:
    explicit Logger(
        std::unique_ptr<ILogFormatter> formatter,
        std::unique_ptr<ILogDestination> destination)
        : m_formatter(std::move(formatter)),
          m_destination(std::move(destination)) {}

    void log(const QString& message) {
        auto formatted = m_formatter->format(message);
        m_destination->write(formatted);
    }

    void flush() {
        m_destination->flush();
    }
};

#endif // LOGGER_H

3.5 使用示例

cpp 复制代码
int main() {
    // 构建不同组合:格式 × 目的地
    auto logger1 = std::make_unique<Logger>(
        std::make_unique<SimpleFormatter>(),
        std::make_unique<ConsoleDestination>()
    );

    auto logger2 = std::make_unique<Logger>(
        std::make_unique<TimestampFormatter>(),
        std::make_unique<FileDestination>("app.log")
    );

    auto logger3 = std::make_unique<Logger>(
        std::make_unique<SimpleFormatter>(),
        std::make_unique<DebugOutputDestination>()
    );

    logger1->log("Hello from console!");
    logger2->log("Application started.");
    logger3->log("Debug info.");

    return 0;
}

✅ 效果:

  • 仅需 6 个类(2 个格式器 + 3 个目的地 + 1 个桥接类),即可支持任意组合。
  • 若新增一种格式或目的地,只需新增一个类,无需修改已有逻辑。
  • 完美解决 N×M 类爆炸问题。

四、桥接模式的核心优势

  1. 解耦两个独立变化维度:形状与颜色、格式与目的地,可分别演化。
  2. 避免类爆炸 :从 N×M 个类 → N + M 个类 + 1 个桥接类。
  3. 支持运行时动态切换实现:可通过配置或注入灵活更换具体实现。
  4. 符合开闭原则(OCP):新增维度时,无需修改原有代码。

五、进阶技巧:C++ 中的 pImpl 惯用法

在 C++ 中,桥接模式常与 pImpl(Pointer to Implementation) 模式深度融合,实现更彻底的接口与实现分离。

cpp 复制代码
// logger_pimpl.h
#ifndef LOGGER_PIMPL_H
#define LOGGER_PIMPL_H

#include "ilogformatter.h"
#include "ilogdestination.h"
#include <memory>

class LoggerImpl {
public:
    std::unique_ptr<ILogFormatter> formatter;
    std::unique_ptr<ILogDestination> destination;

    LoggerImpl(std::unique_ptr<ILogFormatter> f,
               std::unique_ptr<ILogDestination> d)
        : formatter(std::move(f)), destination(std::move(d)) {}

    void log(const QString& msg) {
        auto formatted = formatter->format(msg);
        destination->write(formatted);
    }

    void flush() {
        destination->flush();
    }
};

class Logger {
private:
    std::unique_ptr<LoggerImpl> m_impl;

public:
    explicit Logger(std::unique_ptr<ILogFormatter> formatter,
                    std::unique_ptr<ILogDestination> destination)
        : m_impl(std::make_unique<LoggerImpl>(std::move(formatter),
                                              std::move(destination))) {}

    void log(const QString& message) {
        m_impl->log(message);
    }

    void flush() {
        m_impl->flush();
    }
};

#endif // LOGGER_PIMPL_H

✅ 优势:

  • 头文件仅暴露 Logger 接口,隐藏内部实现细节。
  • 编译依赖最小化,提升编译速度。
  • 支持细粒度控制内存布局与虚函数表。
  • 是 C++ 中实现桥接模式的"标准姿势"。

六、何时该用桥接模式?

✅ 适用场景:

  • 存在两个及以上独立变化的维度;
  • 维度之间组合方式多,且未来会持续扩展;
  • 不希望因组合导致类数量爆炸;
  • 需要运行时动态切换实现。

❌ 不推荐使用:

  • 维度很少,组合简单(如只有 2~3 种);
  • 仅一次性的功能,无扩展需求;
  • 业务逻辑高度耦合,难以拆分。

七、小结

📌 桥接模式的本质 :

将"继承树 "转化为"组合网",让两个独立变化的维度各自独立演化,彻底告别类爆炸。
🔑 关键思想 :

用"组合"代替"继承",用"接口指针"作为"桥梁",打通抽象与实现之间的壁垒。
🧩 典型应用场景:

  • 图形系统(形状 × 颜色)
  • 跨平台开发(抽象接口 × 平台实现)
  • 日志系统(格式 × 输出目标)
  • 设备驱动(协议 × 硬件)
    🚀 建议 :

在设计阶段就考虑桥接模式,尤其面对复杂维度组合时,提前规划能极大降低后期重构成本。


下一篇预告:(7) 策略模式 / 观察者模式 ------ 动态行为切换与事件通知机制,敬请期待!

相关推荐
haluhalu.1 小时前
深入理解AIAgent(1.3)--- 设计模式
人工智能·python·设计模式·langchain
青少儿编程课堂1 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C/C++考级:任意进制互转与大数多进制加法解析
c++·python·算法·bfs·信息学竞赛
Shadow(⊙o⊙)1 小时前
仿muduo实现高并发服务器——EPOLL设置与移除Poller.hpp管理Channel
运维·服务器·c++
闻缺陷则喜何志丹1 小时前
【C++特性及类库】多线程
开发语言·c++·多线程·语言特性·系统库
多弗朗皮卡丘1 小时前
C++ 哈希(下)
c++·算法·哈希算法
dtq04242 小时前
C++3 - 分支语句if和string变量
开发语言·c++·学习
jimy12 小时前
c++类成员访问只看静态类型
开发语言·c++
156082072192 小时前
银河麒麟 Qt 可执行文件打包说明
开发语言·驱动开发·qt
CCCCCCCCharlie2 小时前
从0到1:一个字节的磁盘之旅
linux·开发语言·c++