C++ 设计模式之代理模式详细介绍

代理模式是 结构型设计模式 的核心成员,其核心目标是:为其他对象提供一种"代理"(中间层),以控制对原对象的访问。代理对象通过包装原对象,可在不修改原对象代码的前提下,增加额外功能(如权限校验、日志记录、缓存、延迟加载),同时保持与原对象一致的接口,对使用者透明。

在 C++ 开发中,代理模式广泛应用于"控制访问""增强功能""优化性能"等场景(如远程服务代理、数据库连接代理、缓存代理、权限代理)。本文将从 核心原理、结构组成、多种实现方式、工程优化、实际应用 等维度,全面解析 C++ 代理模式的设计与落地。

一、代理模式的核心目标与设计思想

1.1 核心目标

  1. 控制访问:限制对原对象的访问(如权限校验、访问频率限制),仅允许符合条件的调用者操作原对象。
  2. 增强功能:在原对象的方法调用前后添加额外逻辑(如日志记录、性能监控、事务管理),实现"无侵入式扩展"。
  3. 优化性能:通过延迟加载(懒初始化)、缓存结果等方式,减少原对象的创建开销或重复计算。
  4. 隔离原对象:原对象可能是远程服务、重型组件或不易直接访问的资源(如数据库),代理对象作为中间层屏蔽底层细节,让使用者无需关心原对象的具体位置或实现。

1.2 核心设计思想

  • 接口一致性:代理对象与原对象实现相同的抽象接口,确保使用者可以无差别地调用代理对象和原对象(符合"里氏替换原则")。
  • 组合复用:代理对象通过"组合"而非"继承"持有原对象的引用/指针,避免继承带来的紧耦合,同时支持动态切换原对象(符合"合成复用原则")。
  • 无侵入扩展:无需修改原对象的代码,即可通过代理对象添加新功能(符合"开闭原则")。

二、代理模式的核心结构(4大角色)

代理模式的结构严格遵循"接口一致"和"组合复用"原则,核心由 4 个角色组成:

角色 职责描述
抽象主题(Subject) 定义原对象和代理对象的统一接口(抽象类/纯虚函数),确保两者行为一致。
真实主题(RealSubject) 被代理的"原对象",包含核心业务逻辑(如数据库操作、远程服务调用)。
代理(Proxy) 代理对象,实现抽象主题接口;持有真实主题的引用/指针;在调用原对象方法前后添加额外逻辑,最终转发调用到原对象。
客户端(Client) 使用者,通过抽象主题接口调用代理对象(无需知道代理的存在,以为直接调用原对象)。

结构示意图

复制代码
┌─────────────────┐
│  Subject(抽象) │  ← 统一接口
├─────────────────┤
│ + request()     │
└────────┬────────┘
         │
         ├─────────────┐
         ▼             ▼
┌─────────────────┐  ┌─────────────────┐
│ RealSubject(真实)│  │   Proxy(代理) │
├─────────────────┤  ├─────────────────┤
│ + request() {   │  │ - real_subject: │
│     核心逻辑    │  │   Subject*      │
│ }               │  │ + request() {   │
└─────────────────┘  │   额外逻辑      │
                     │   real_subject->request();
                     │ }               │
                     └─────────────────┘

三、代理模式的分类与 C++ 实现

根据应用场景,代理模式可分为 静态代理、动态代理、远程代理、缓存代理、延迟加载代理 等,其中静态代理是基础,动态代理是进阶(依赖反射/模板)。以下按"从基础到复杂"的顺序详解实现方式:

3.1 基础版:静态代理(最常用)

静态代理是指 代理类在编译期就已确定,代理对象与原对象实现相同的抽象接口,直接持有原对象的引用,硬编码额外逻辑。

应用场景
  • 需为特定原对象添加固定额外功能(如日志、权限校验),且无需动态切换原对象。
完整实现代码(日志代理示例)

以"用户管理系统"为例:UserService 是真实主题(核心业务:查询用户),UserServiceProxy 是代理对象(添加日志记录功能)。

cpp 复制代码
#include <iostream>
#include <string>
#include <chrono>  // 用于时间戳(日志用)

// 1. 抽象主题(Subject):定义统一接口
class UserService {
public:
    virtual ~UserService() = default;  // 虚析构,确保子类析构被调用
    // 核心业务方法:根据用户ID查询用户名
    virtual std::string getUserById(int userId) const = 0;
};

// 2. 真实主题(RealSubject):实现核心业务逻辑
class RealUserService : public UserService {
public:
    std::string getUserById(int userId) const override {
        // 模拟数据库查询(核心业务)
        std::cout << "[RealUserService] 正在查询数据库,用户ID:" << userId << std::endl;
        // 模拟查询结果
        switch (userId) {
            case 1001: return "张三";
            case 1002: return "李四";
            default: return "未知用户";
        }
    }
};

// 3. 代理(Proxy):实现抽象接口,持有真实主题,添加额外逻辑
class UserServiceProxy : public UserService {
public:
    // 构造函数:注入真实主题(依赖注入,解耦代理与真实主题)
    explicit UserServiceProxy(UserService* realService) 
        : real_service_(realService) {
        if (real_service_ == nullptr) {
            throw std::invalid_argument("真实主题不能为空!");
        }
    }

    ~UserServiceProxy() override {
        // 注意:若代理对象负责管理真实主题的生命周期,可在此处delete
        // delete real_service_; 
    }

    // 实现统一接口:添加日志逻辑,转发调用到真实主题
    std::string getUserById(int userId) const override {
        // 1. 调用前额外逻辑:记录日志(时间戳、调用参数)
        auto now = std::chrono::system_clock::now();
        std::time_t now_time = std::chrono::system_clock::to_time_t(now);
        std::cout << "[UserServiceProxy] 日志:" << std::ctime(&now_time) 
                  << " 调用 getUserById,参数 userId:" << userId << std::endl;

        // 2. 转发调用到真实主题(核心业务逻辑)
        std::string result = real_service_->getUserById(userId);

        // 3. 调用后额外逻辑:记录结果
        std::cout << "[UserServiceProxy] 日志:查询结果:" << result << std::endl;

        return result;
    }

private:
    UserService* real_service_;  // 持有真实主题的指针(组合复用)
};

// 4. 客户端(Client):通过抽象接口调用代理,无需知道真实主题
int main() {
    try {
        // 创建真实主题
        UserService* realService = new RealUserService();
        // 创建代理对象(注入真实主题)
        UserService* proxy = new UserServiceProxy(realService);

        // 客户端调用代理(以为直接调用真实主题)
        std::cout << "客户端查询用户ID=1001:" << proxy->getUserById(1001) << std::endl;
        std::cout << "\n客户端查询用户ID=1003:" << proxy->getUserById(1003) << std::endl;

        // 释放资源(注意:避免重复删除,若代理未管理真实主题,客户端需手动释放)
        delete proxy;       // 代理对象析构(不删除真实主题)
        delete realService; // 手动释放真实主题
    } catch (const std::exception& e) {
        std::cerr << "错误:" << e.what() << std::endl;
    }

    return 0;
}
运行结果
复制代码
[UserServiceProxy] 日志: Tue Dec  2 15:30:00 2025
 调用 getUserById,参数 userId:1001
[RealUserService] 正在查询数据库,用户ID:1001
[UserServiceProxy] 日志:查询结果:张三
客户端查询用户ID=1001:张三

[UserServiceProxy] 日志: Tue Dec  2 15:30:00 2025
 调用 getUserById,参数 userId:1003
[RealUserService] 正在查询数据库,用户ID:1003
[UserServiceProxy] 日志:查询结果:未知用户
客户端查询用户ID=1003:未知用户
核心优点
  • 实现简单:逻辑清晰,无复杂语法依赖,编译期即可检查错误。
  • 无侵入扩展 :不修改 RealUserService 代码,即可添加日志功能(符合开闭原则)。
  • 接口一致:客户端无需修改代码,直接替换为代理对象即可使用。
核心缺点
  • 紧耦合 :代理类与真实主题类一一对应(如 UserServiceProxy 仅适配 UserService),新增真实主题需同步新增代理类,代码冗余。
  • 灵活性差:额外逻辑(如日志)硬编码在代理类中,若需修改逻辑(如日志格式),需修改代理类代码。

3.2 进阶版 1:模板静态代理(解决代码冗余)

为解决静态代理"一对一"的代码冗余问题,可使用 C++ 模板实现 通用代理类,支持任意实现了相同接口的真实主题,无需为每个真实主题编写单独的代理类。

实现思路
  • 代理类通过模板参数接收真实主题类型,确保代理与真实主题实现相同的抽象接口。
  • 额外逻辑(如日志、权限)封装在模板代理类中,对所有真实主题复用。
完整实现代码(通用日志代理)
cpp 复制代码
#include <iostream>
#include <string>
#include <chrono>
#include <type_traits>  // 用于类型检查(确保真实主题实现接口)

// 1. 抽象主题(Subject):统一接口
class UserService {
public:
    virtual ~UserService() = default;
    virtual std::string getUserById(int userId) const = 0;
};

// 2. 真实主题(RealSubject):核心业务
class RealUserService : public UserService {
public:
    std::string getUserById(int userId) const override {
        std::cout << "[RealUserService] 数据库查询用户ID:" << userId << std::endl;
        return userId == 1001 ? "张三" : "未知用户";
    }
};

// 新增真实主题:管理员服务(同样实现 UserService 接口)
class AdminService : public UserService {
public:
    std::string getUserById(int userId) const override {
        std::cout << "[AdminService] 管理员查询用户ID:" << userId << std::endl;
        return userId == 0 ? "超级管理员" : "普通管理员";
    }
};

// 3. 模板代理类(通用代理,支持任意实现 UserService 接口的真实主题)
template <typename RealSubject>
class LogProxy : public UserService {
public:
    static_assert(std::is_base_of<UserService, RealSubject>::value, 
                  "RealSubject 必须继承自 UserService!");  // 类型检查

    LogProxy() : real_subject_(new RealSubject()) {}  // 内部创建真实主题(或通过构造函数注入)
    explicit LogProxy(RealSubject* realSubject) : real_subject_(realSubject) {}

    ~LogProxy() override {
        delete real_subject_;  // 代理管理真实主题生命周期
    }

    // 实现统一接口:复用日志逻辑
    std::string getUserById(int userId) const override {
        // 调用前日志
        auto now = std::chrono::system_clock::now();
        std::time_t now_time = std::chrono::system_clock::to_time_t(now);
        std::cout << "[LogProxy] 日志:" << std::ctime(&now_time) 
                  << " 调用 getUserById,参数:" << userId << std::endl;

        // 转发调用
        std::string result = real_subject_->getUserById(userId);

        // 调用后日志
        std::cout << "[LogProxy] 日志:查询结果:" << result << std::endl;
        return result;
    }

private:
    RealSubject* real_subject_;  // 模板类型的真实主题指针
};

// 客户端调用
int main() {
    // 1. 代理 RealUserService
    UserService* userProxy = new LogProxy<RealUserService>();
    std::cout << "查询普通用户:" << userProxy->getUserById(1001) << std::endl;

    // 2. 代理 AdminService(无需新增代理类,直接复用 LogProxy)
    UserService* adminProxy = new LogProxy<AdminService>();
    std::cout << "\n查询管理员:" << adminProxy->getUserById(0) << std::endl;

    // 释放资源
    delete userProxy;
    delete adminProxy;

    return 0;
}
运行结果
复制代码
[LogProxy] 日志: Tue Dec  2 15:35:00 2025
 调用 getUserById,参数:1001
[RealUserService] 数据库查询用户ID:1001
[LogProxy] 日志:查询结果:张三
查询普通用户:张三

[LogProxy] 日志: Tue Dec  2 15:35:00 2025
 调用 getUserById,参数:0
[AdminService] 管理员查询用户ID:0
[LogProxy] 日志:查询结果:超级管理员
查询管理员:超级管理员
核心优势
  • 通用复用:一个代理类适配所有实现了抽象接口的真实主题,解决代码冗余问题。
  • 类型安全 :通过 static_assert 确保真实主题必须实现抽象接口,编译期报错。
  • 灵活注入:支持通过构造函数注入真实主题(如外部创建的重型对象),或内部自动创建。
适用场景
  • 多个真实主题实现相同接口,且需要添加相同的额外逻辑(如统一日志、统一权限校验)。

3.3 进阶版 2:延迟加载代理(优化性能)

延迟加载(懒初始化)代理的核心是:真实主题在第一次被调用时才创建,而非程序启动时,避免提前占用资源(适用于重型对象,如数据库连接池、大型缓存)。

实现思路
  • 代理对象初始化时不创建真实主题,仅在第一次调用核心方法时创建。
  • 后续调用直接复用已创建的真实主题,避免重复初始化开销。
完整实现代码(数据库连接代理)
cpp 复制代码
#include <iostream>
#include <string>
#include <mutex>  // 线程安全(多线程场景必备)

// 1. 抽象主题:数据库连接接口
class DBConnection {
public:
    virtual ~DBConnection() = default;
    virtual void connect() = 0;  // 核心方法:连接数据库
    virtual void executeSQL(const std::string& sql) = 0;  // 执行SQL
};

// 2. 真实主题:MySQL数据库连接(重型对象,创建开销大)
class MySQLConnection : public DBConnection {
public:
    MySQLConnection() {
        // 模拟重型对象的初始化(如创建网络连接、初始化驱动)
        std::cout << "[MySQLConnection] 初始化MySQL连接(耗时操作)" << std::endl;
    }

    void connect() override {
        std::cout << "[MySQLConnection] 成功连接到MySQL数据库" << std::endl;
    }

    void executeSQL(const std::string& sql) override {
        std::cout << "[MySQLConnection] 执行SQL:" << sql << std::endl;
    }
};

// 3. 延迟加载代理:第一次调用时才创建真实主题
class LazyDBProxy : public DBConnection {
public:
    LazyDBProxy() : real_conn_(nullptr) {}  // 初始化时不创建真实主题

    ~LazyDBProxy() override {
        delete real_conn_;  // 代理管理真实主题生命周期
    }

    // 延迟创建真实主题(线程安全)
    void initRealConnection() {
        if (real_conn_ == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_);  // 多线程安全
            if (real_conn_ == nullptr) {  // 双重检查锁定(DCLP)
                real_conn_ = new MySQLConnection();  // 第一次调用时创建
            }
        }
    }

    // 代理 connect 方法
    void connect() override {
        initRealConnection();  // 确保真实主题已创建
        real_conn_->connect();  // 转发调用
    }

    // 代理 executeSQL 方法
    void executeSQL(const std::string& sql) override {
        initRealConnection();  // 确保真实主题已创建
        real_conn_->executeSQL(sql);  // 转发调用
    }

private:
    DBConnection* real_conn_;  // 真实主题指针(初始为null)
    std::mutex mutex_;  // 多线程安全锁
};

// 客户端调用
int main() {
    std::cout << "程序启动,创建代理对象(未创建数据库连接)" << std::endl;
    LazyDBProxy proxy;  // 此时未创建 MySQLConnection

    // 第一次调用:触发真实主题创建
    std::cout << "\n第一次调用 connect:" << std::endl;
    proxy.connect();

    // 第二次调用:复用已创建的真实主题
    std::cout << "\n第二次调用 executeSQL:" << std::endl;
    proxy.executeSQL("SELECT * FROM users");

    return 0;
}
运行结果
复制代码
程序启动,创建代理对象(未创建数据库连接)

第一次调用 connect:
[MySQLConnection] 初始化MySQL连接(耗时操作)
[MySQLConnection] 成功连接到MySQL数据库

第二次调用 executeSQL:
[MySQLConnection] 执行SQL:SELECT * FROM users
核心优势
  • 优化启动性能:重型对象延迟到第一次使用时创建,减少程序启动时间和初始内存占用。
  • 线程安全:通过双重检查锁定(DCLP)+ 互斥锁,确保多线程场景下真实主题仅被创建一次。
  • 透明性:客户端无需关心真实主题的创建时机,调用方式与直接使用真实主题一致。
适用场景
  • 真实主题是重型对象(如数据库连接、大型缓存、复杂组件),创建开销大且可能不被使用。
  • 程序启动时需初始化大量组件,需优化启动速度。

3.4 进阶版 3:缓存代理(减少重复计算)

缓存代理的核心是:缓存真实主题方法的调用结果,当再次以相同参数调用时,直接返回缓存结果,避免重复计算或重复查询(适用于计算密集型或IO密集型方法)。

实现思路
  • 代理对象内部维护一个缓存容器(如 std::unordered_map),存储"参数 → 结果"的映射。
  • 调用真实主题方法前,先检查缓存:若存在缓存结果,直接返回;若不存在,调用真实主题并缓存结果。
完整实现代码(计算缓存代理)
cpp 复制代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <mutex>  // 线程安全缓存

// 1. 抽象主题:计算接口(如复杂数学计算)
class Calculator {
public:
    virtual ~Calculator() = default;
    // 核心方法:计算斐波那契数列(递归实现,重复计算多)
    virtual int fibonacci(int n) const = 0;
};

// 2. 真实主题:斐波那契计算器(计算密集型,重复参数开销大)
class FibonacciCalculator : public Calculator {
public:
    int fibonacci(int n) const override {
        if (n <= 1) return n;
        std::cout << "[FibonacciCalculator] 计算 fib(" << n << ") = fib(" << n-1 << ") + fib(" << n-2 << ")" << std::endl;
        return fibonacci(n-1) + fibonacci(n-2);
    }
};

// 3. 缓存代理:缓存计算结果,避免重复计算
class CacheProxy : public Calculator {
public:
    explicit CacheProxy(Calculator* realCalc) : real_calc_(realCalc) {}

    ~CacheProxy() override {
        delete real_calc_;
    }

    int fibonacci(int n) const override {
        // 1. 检查缓存:存在则直接返回
        {
            std::lock_guard<std::mutex> lock(mutex_);  // 缓存读写线程安全
            auto it = cache_.find(n);
            if (it != cache_.end()) {
                std::cout << "[CacheProxy] 命中缓存:fib(" << n << ") = " << it->second << std::endl;
                return it->second;
            }
        }

        // 2. 缓存未命中:调用真实主题计算
        int result = real_calc_->fibonacci(n);

        // 3. 缓存结果
        {
            std::lock_guard<std::mutex> lock(mutex_);
            cache_.emplace(n, result);
            std::cout << "[CacheProxy] 缓存 fib(" << n << ") = " << result << std::endl;
        }

        return result;
    }

private:
    Calculator* real_calc_;  // 真实计算器
    mutable std::unordered_map<int, int> cache_;  // 缓存容器(mutable允许const方法修改)
    mutable std::mutex mutex_;  // 缓存锁(mutable允许const方法使用)
};

// 客户端调用
int main() {
    Calculator* proxy = new CacheProxy(new FibonacciCalculator());

    // 第一次计算 fib(5):无缓存,触发真实计算
    std::cout << "第一次计算 fib(5):" << std::endl;
    std::cout << "结果:" << proxy->fibonacci(5) << std::endl;

    // 第二次计算 fib(5):命中缓存,直接返回
    std::cout << "\n第二次计算 fib(5):" << std::endl;
    std::cout << "结果:" << proxy->fibonacci(5) << std::endl;

    // 计算 fib(6):依赖 fib(5)(已缓存)和 fib(4)(需计算)
    std::cout << "\n计算 fib(6):" << std::endl;
    std::cout << "结果:" << proxy->fibonacci(6) << std::endl;

    delete proxy;
    return 0;
}
运行结果
复制代码
第一次计算 fib(5):
[FibonacciCalculator] 计算 fib(5) = fib(4) + fib(3)
[FibonacciCalculator] 计算 fib(4) = fib(3) + fib(2)
[FibonacciCalculator] 计算 fib(3) = fib(2) + fib(1)
[FibonacciCalculator] 计算 fib(2) = fib(1) + fib(0)
[CacheProxy] 缓存 fib(2) = 1
[CacheProxy] 缓存 fib(3) = 2
[FibonacciCalculator] 计算 fib(2) = fib(1) + fib(0)
[CacheProxy] 缓存 fib(4) = 3
[FibonacciCalculator] 计算 fib(3) = fib(2) + fib(1)
[CacheProxy] 缓存 fib(5) = 5
结果:5

第二次计算 fib(5):
[CacheProxy] 命中缓存:fib(5) = 5
结果:5

计算 fib(6):
[FibonacciCalculator] 计算 fib(6) = fib(5) + fib(4)
[CacheProxy] 命中缓存:fib(5) = 5
[CacheProxy] 命中缓存:fib(4) = 3
[CacheProxy] 缓存 fib(6) = 8
结果:8
核心优势
  • 提升性能:避免重复计算/查询,尤其适用于计算密集型或IO密集型方法。
  • 线程安全:缓存读写加锁,支持多线程并发访问。
  • 灵活扩展:可添加缓存过期策略(如时间过期、LRU淘汰),适配不同场景。
适用场景
  • 方法调用参数固定,结果不变(纯函数)。
  • 方法计算/查询耗时久(如数据库查询、复杂数学计算、远程API调用)。

3.5 高级版:动态代理(基于反射/Qt元对象)

静态代理(包括模板代理)的局限是:代理类与抽象接口强绑定,若抽象接口修改(如新增方法),代理类需同步修改 。动态代理则是在 运行时动态生成代理对象,无需提前编写代理类,适配任意接口。

C++ 无原生反射机制,实现动态代理需依赖第三方库(如 Qt 元对象系统、Boost.Reflection)或手动解析函数指针(复杂且不通用)。以下以 Qt 为例,演示动态代理的实现(Qt 元对象系统支持信号槽和动态调用)。

Qt 动态代理实现(核心思路)
  1. 代理类继承 QObject,利用 Qt 元对象系统(QMetaObject)获取真实主题的方法信息。
  2. 重写 QObject::event 或使用 Q_INVOKABLE 标记方法,实现动态转发调用。
  3. 代理类可在转发调用前后添加额外逻辑(如日志、权限),无需关心真实主题的具体接口。
关键代码片段
cpp 复制代码
#include <QObject>
#include <QMetaObject>
#include <QMetaMethod>
#include <QDebug>

// 真实主题(需继承 QObject,且方法用 Q_INVOKABLE 标记)
class RealObject : public QObject {
    Q_OBJECT
public:
    Q_INVOKABLE void doSomething(const QString& msg) {
        qDebug() << "[RealObject] doSomething:" << msg;
    }

    Q_INVOKABLE int add(int a, int b) {
        qDebug() << "[RealObject] add:" << a << "+" << b;
        return a + b;
    }
};

// 动态代理类(继承 QObject,动态转发调用)
class DynamicProxy : public QObject {
    Q_OBJECT
public:
    explicit DynamicProxy(QObject* realObject, QObject* parent = nullptr)
        : QObject(parent), real_object_(realObject) {}

    // 动态调用真实主题的方法
    QVariant invokeMethod(const QString& methodName, const QVariantList& args) {
        if (!real_object_) return QVariant();

        // 1. 获取真实主题的元对象
        const QMetaObject* metaObj = real_object_->metaObject();

        // 2. 根据方法名和参数类型查找方法
        int methodIndex = metaObj->indexOfMethod(
            QMetaObject::normalizedSignature(
                (methodName + "(" + QString(args.size(), 'QVariant,') + ")").toUtf8().constData()
            )
        );
        if (methodIndex == -1) {
            qWarning() << "方法不存在:" << methodName;
            return QVariant();
        }

        QMetaMethod method = metaObj->method(methodIndex);

        // 3. 调用前额外逻辑(如日志)
        qDebug() << "[DynamicProxy] 调用方法:" << methodName << ",参数:" << args;

        // 4. 动态转发调用
        QVariant result;
        if (!method.invoke(real_object_, Qt::DirectConnection,
                          Q_RETURN_ARG(QVariant, result),
                          Q_ARG(QVariantList, args))) {
            qWarning() << "方法调用失败:" << methodName;
        }

        // 5. 调用后额外逻辑(如日志)
        qDebug() << "[DynamicProxy] 方法返回:" << result;

        return result;
    }

private:
    QObject* real_object_;  // 真实主题(支持任意 QObject 子类)
};

// 客户端调用
int main(int argc, char* argv[]) {
    QCoreApplication app(argc, argv);

    RealObject realObj;
    DynamicProxy proxy(&realObj);

    // 动态调用 doSomething 方法
    proxy.invokeMethod("doSomething", QVariantList() << "Hello Dynamic Proxy");

    // 动态调用 add 方法
    QVariant addResult = proxy.invokeMethod("add", QVariantList() << 10 << 20);
    qDebug() << "add 最终结果:" << addResult.toInt();

    return app.exec();
}
核心优势
  • 完全解耦 :代理类不依赖具体抽象接口,支持任意 QObject 子类,接口修改无需修改代理类。
  • 动态灵活:运行时动态查找和调用方法,支持新增方法无需重新编译代理类。
局限
  • 依赖第三方库(如 Qt),不适合纯 C++ 项目。
  • 性能略低于静态代理(运行时查找方法有开销)。
  • 仅支持符合特定规则的方法(如 Qt 需 Q_INVOKABLE 标记)。
适用场景
  • 接口频繁变化,或需代理大量不同接口的对象(如插件系统、通用框架)。
  • 基于 Qt 等支持反射的框架开发。

四、代理模式的关键问题与工程优化

4.1 内存管理(避免野指针/内存泄漏)

代理对象持有真实主题的指针/引用,若管理不当易导致野指针或内存泄漏,常见解决方案:

  1. 代理管理生命周期 :代理对象创建真实主题(如 new RealSubject()),并在析构时 delete,确保资源释放。

  2. 外部管理生命周期 :真实主题由客户端创建,代理仅持有引用(如 std::weak_ptr),避免循环引用。

  3. 智能指针 :使用 std::shared_ptr/std::weak_ptr 管理真实主题,自动处理生命周期(推荐):

    cpp 复制代码
    // 智能指针版代理
    class UserServiceProxy : public UserService {
    public:
        explicit UserServiceProxy(std::shared_ptr<UserService> realService)
            : real_service_(realService) {}
    
        std::string getUserById(int userId) const override {
            return real_service_->getUserById(userId);  // 无需手动释放
        }
    
    private:
        std::shared_ptr<UserService> real_service_;  // 智能指针自动管理
    };

4.2 线程安全(多线程场景必备)

多线程环境下,代理的核心风险是:

  • 延迟加载时真实主题被多次创建;
  • 缓存读写冲突;
  • 观察者列表并发修改。

解决方案:

  1. 延迟加载:双重检查锁定(DCLP)+ std::mutex
  2. 缓存/观察者列表:std::lock_guard/std::unique_lock 保护读写操作;
  3. 避免共享状态:若代理无共享状态(如仅转发调用),可无需加锁。

4.3 接口一致性(避免使用者感知代理)

代理模式的核心优势之一是"对客户端透明",需确保:

  1. 代理类与真实主题严格实现相同的抽象接口(包括方法名、参数类型、返回值、异常类型);
  2. 避免在代理中添加额外的公有方法(除非客户端明确需要);
  3. 若原对象有虚析构函数,代理类也必须实现虚析构(避免删除代理时漏析构真实主题)。

4.4 避免过度代理(防止设计冗余)

代理模式的滥用会导致系统复杂度上升,需满足以下条件才使用:

  1. 确实需要控制访问、增强功能或优化性能;
  2. 无法通过修改原对象代码实现(如原对象是第三方库、不可修改);
  3. 代理的额外逻辑具有复用价值(如统一日志、缓存)。

五、代理模式的适用场景与反场景

5.1 适用场景

  1. 控制访问
    • 权限校验(如仅管理员可调用某些方法,代理拦截无权限调用);
    • 访问频率限制(如接口限流,代理拒绝超过频率的调用);
    • 隐藏真实对象(如远程服务代理,客户端无需知道服务端地址)。
  2. 增强功能
    • 日志记录(调用时间、参数、结果);
    • 性能监控(统计方法调用耗时);
    • 事务管理(数据库操作的开始/提交/回滚)。
  3. 优化性能
    • 延迟加载(重型对象懒初始化);
    • 缓存(重复调用结果复用);
    • 负载均衡(远程代理分发请求到多个服务端)。
  4. 隔离依赖
    • 远程代理(屏蔽网络通信细节,如 RPC 框架);
    • 第三方库代理(封装第三方库接口,便于后续替换)。

5.2 反场景(不建议使用)

  1. 简单对象无额外需求:若原对象简单,无需控制访问或增强功能,直接调用原对象更简洁。
  2. 接口频繁变化且无反射支持:静态代理需同步修改接口,导致维护成本过高(可考虑动态代理)。
  3. 性能敏感且无复用价值:代理的额外逻辑(如加锁、日志)会带来性能开销,若仅为单个场景添加功能,不如直接修改原对象。
  4. 循环依赖风险:代理与真实主题相互引用,且未使用弱指针,易导致内存泄漏。

六、代理模式与其他设计模式的区别

6.1 代理模式 vs 装饰器模式

  • 核心目标不同
    • 代理模式:核心是"控制访问"(如权限、延迟加载、缓存),代理对象通常知道原对象的存在,且可能完全替代原对象。
    • 装饰器模式:核心是"增强功能"(如动态添加责任),装饰器对象是为了给原对象添加新功能,通常是多层装饰叠加。
  • 使用场景不同
    • 需控制访问、优化性能时用代理;
    • 需动态添加功能、多层组合时用装饰器。

6.2 代理模式 vs 适配器模式

  • 核心目标不同
    • 代理模式:保持原接口不变,控制对原对象的访问或增强功能。
    • 适配器模式:转换原对象的接口(如旧接口适配新接口),解决接口不兼容问题。
  • 使用者感知不同
    • 代理模式:客户端无感知,以为直接调用原对象;
    • 适配器模式:客户端需调用适配后的新接口。

6.3 代理模式 vs 工厂模式

  • 核心目标不同
    • 代理模式:控制对已有对象的访问,增强功能;
    • 工厂模式:创建对象,封装对象的创建逻辑。
  • 角色关系不同
    • 代理模式:代理对象持有原对象的引用,是"包装者"与"被包装者"的关系;
    • 工厂模式:工厂类不持有产品对象,是"创建者"与"产品"的关系。

七、C++ 成熟库中的代理模式应用

  1. Qt 框架
    • QNetworkAccessManager:网络请求代理,屏蔽不同协议(HTTP/HTTPS)的底层细节,支持缓存、超时控制;
    • QSortFilterProxyModel:模型代理,对 QAbstractItemModel 进行排序、过滤,不修改原模型数据。
  2. Boost 库
    • boost::asio::ip::tcp::socket:网络 socket 代理,封装底层操作系统的 socket 接口,提供统一的异步操作接口;
    • boost::proxy:通用代理模板,支持透明转发调用,可添加自定义逻辑。
  3. RPC 框架(如 gRPC)
    • 远程代理:客户端通过代理对象调用远程服务,代理屏蔽网络通信、序列化/反序列化细节,客户端以为调用本地方法。
  4. 数据库连接池
    • 连接代理:池化的连接对象是真实数据库连接的代理,负责连接的复用、超时回收、状态检查。

八、总结与核心原则

代理模式的核心是 "透明代理,增强控制",C++ 实现的关键在于:

  1. 保持接口一致性:代理与真实主题实现相同接口,对客户端透明;
  2. 组合复用优先:通过持有真实主题的引用/指针,避免继承带来的紧耦合;
  3. 解决工程问题:重点处理内存管理、线程安全、性能优化(如延迟加载、缓存);
  4. 避免过度设计:仅在需要控制访问、增强功能或优化性能时使用。

核心原则

  1. 开闭原则:无需修改原对象和客户端代码,即可通过代理添加新功能;
  2. 单一职责:代理类仅负责代理逻辑(如日志、缓存),真实主题仅负责核心业务;
  3. 里氏替换:代理对象可替代真实主题,客户端无需修改调用代码;
  4. 最小知识:客户端无需了解真实主题的具体实现,仅通过抽象接口交互。

在实际开发中,静态代理(含模板代理)是最常用的实现方式,适用于大多数场景;延迟加载代理和缓存代理是性能优化的重要手段;动态代理适用于接口频繁变化或通用框架开发。掌握代理模式的设计思想和工程实践,能有效提升代码的可维护性、可扩展性和性能。

相关推荐
扣脚大汉在网络23 分钟前
关于一句话木马
开发语言·网络安全
暗然而日章25 分钟前
C++基础:Stanford CS106L学习笔记 5 内存与指针
c++·笔记·学习
学习路上_write25 分钟前
FREERTOS_定时器——创建和基本使用
c语言·开发语言·c++·stm32·嵌入式硬件
学技术的大胜嗷26 分钟前
如何在 VSCode 中高效开发和调试 C++ 程序:面向用过 Visual Studio 的小白
c++·vscode·visual studio
李斯维27 分钟前
第12章 使用 Shell:变量和选项
linux·unix
ExiFengs28 分钟前
使用Java 8函数式编程优雅处理多层嵌套数据
java·开发语言·python
雨中飘荡的记忆28 分钟前
设计模式之组合模式详解
设计模式·组合模式
liu****30 分钟前
10.指针详解(六)
c语言·开发语言·数据结构·c++·算法
美味小鱼31 分钟前
DupFinder:一个用 Rust 编写的高性能重复文件查找工具
开发语言·后端·rust
大聪明-PLUS31 分钟前
如何提高 FFmpeg 中的视频流解码速度
linux·嵌入式·arm·smarc