【C++】代理模式

发布于:2025-07-01 ⋅ 阅读:(24) ⋅ 点赞:(0)

代理模式(Proxy Pattern)是一种【结构型】设计模式,它允许通过代理对象控制对另一个对象(目标对象)的访问。代理对象在客户端和目标对象之间充当中间层,负责处理与目标对象的交互,例如延迟加载、访问控制、缓存或日志记录等。

一、模式核心概念与结构

代理模式包含三个核心角色:

  1. 抽象主题(Subject):定义目标对象和代理对象的共同接口,客户端通过该接口与目标对象交互。
  2. 目标对象(Real Subject):实现抽象主题接口,提供实际的业务逻辑。
  3. 代理对象(Proxy):实现抽象主题接口,持有目标对象的引用,并在调用目标对象方法前后执行额外操作。

二、C++ 实现示例:图像加载代理

以下是一个经典的代理模式示例,演示如何使用代理模式实现图像的延迟加载:

#include <iostream>
#include <string>
#include <memory>

// 抽象主题:图像
class Image {
public:
    virtual ~Image() = default;
    virtual void display() const = 0;
};

// 目标对象:真实图像
class RealImage : public Image {
private:
    std::string filename;

public:
    RealImage(const std::string& fname) : filename(fname) {
        loadFromDisk();
    }
    
    void display() const override {
        std::cout << "Displaying image: " << filename << std::endl;
    }
    
    // 模拟从磁盘加载图像(耗时操作)
    void loadFromDisk() const {
        std::cout << "Loading image: " << filename << std::endl;
    }
};

// 代理对象:图像代理
class ProxyImage : public Image {
private:
    std::string filename;
    mutable std::shared_ptr<RealImage> realImage;  // 延迟加载

public:
    ProxyImage(const std::string& fname) : filename(fname), realImage(nullptr) {}
    
    void display() const override {
        if (!realImage) {
            // 延迟加载:首次调用时才创建真实对象
            realImage = std::make_shared<RealImage>(filename);
        }
        realImage->display();
    }
};

// 客户端代码
int main() {
    // 使用代理对象加载图像
    Image* image = new ProxyImage("test.jpg");
    
    // 第一次调用:加载并显示
    std::cout << "First display:" << std::endl;
    image->display();
    
    std::cout << "\n";
    
    // 第二次调用:直接显示(无需重新加载)
    std::cout << "Second display:" << std::endl;
    image->display();
    
    delete image;
    return 0;
}

三、代理模式的关键特性

  1. 接口一致性
    • 代理对象和目标对象实现相同的接口,客户端无需区分使用的是代理还是真实对象。
  2. 控制访问
    • 代理可以在访问目标对象前后添加额外逻辑,如权限检查、缓存、延迟加载等。
  3. 解耦客户端与目标对象
    • 客户端通过代理间接访问目标对象,降低了系统的耦合度。

四、代理模式的常见类型

  1. 远程代理(Remote Proxy)
    • 为远程对象提供本地代理,隐藏网络通信细节。
    • 例如:RPC(远程过程调用)、Web 服务客户端。
  2. 虚拟代理(Virtual Proxy)
    • 延迟创建开销大的对象,在需要时才初始化。
    • 例如:图像延迟加载、大型文件的按需读取。
  3. 保护代理(Protection Proxy)
    • 控制对敏感对象的访问,进行权限检查。
    • 例如:用户权限验证、防火墙代理。
  4. 智能引用代理(Smart Reference Proxy)
    • 在访问对象时添加额外操作,如引用计数、锁定。
    • 例如:智能指针、数据库连接池。
  5. 缓存代理(Caching Proxy)
    • 缓存目标对象的结果,提高性能。
    • 例如:HTTP 缓存代理、数据库查询缓存。

五、应用场景

  1. 延迟加载
    • 当创建对象成本高时,如加载大型图像或初始化复杂资源。
  2. 访问控制
    • 限制对某些对象的访问,如基于用户权限的资源访问。
  3. 日志记录
    • 在方法调用前后添加日志,记录操作细节。
  4. 资源池管理
    • 管理共享资源,如数据库连接、线程池。
  5. 远程通信
    • 隐藏网络通信细节,提供本地接口访问远程服务。

六、代理模式与其他设计模式的关系

  1. 装饰模式
    • 代理模式控制对象访问,装饰模式增强对象功能。
    • 代理模式的重点是访问控制,装饰模式的重点是功能扩展。
  2. 适配器模式
    • 适配器模式改变对象接口,代理模式保持接口一致。
    • 适配器模式用于不兼容接口的转换,代理模式用于控制访问。
  3. 外观模式
    • 代理模式代表单个对象,外观模式代表子系统。
    • 代理模式的粒度更细,外观模式的粒度更粗。

七、C++ 标准库中的代理模式应用

  1. 智能指针
    • std::shared_ptrstd::unique_ptr是典型的代理模式实现,控制对资源的访问和生命周期。
  2. 迭代器
    • STL 迭代器作为容器元素的代理,隐藏底层数据结构的访问细节。
  3. 流缓冲区
    • std::streambuf作为实际 IO 设备的代理,提供缓冲功能。
  4. 函数对象(Functor)
    • 函数对象可以看作是函数的代理,封装额外行为(如状态)。

八、优缺点分析

优点:

  • 降低耦合:客户端与目标对象解耦,提高系统可维护性。
  • 增强灵活性:可以在不修改目标对象的情况下添加新功能。
  • 优化性能:通过延迟加载、缓存等机制提升系统效率。

缺点:

  • 增加复杂度:引入代理对象可能使系统设计更复杂。
  • 性能开销:代理层可能带来额外的调用开销。
  • 调试困难:多层代理可能导致调试和理解代码变得困难。

九、实战案例:数据库连接代理

以下是一个数据库连接代理的实现示例,用于控制数据库连接的生命周期和权限:

#include <iostream>
#include <string>
#include <memory>
#include <mutex>

// 抽象主题:数据库连接
class DBConnection {
public:
    virtual ~DBConnection() = default;
    virtual void connect() = 0;
    virtual void executeQuery(const std::string& query) = 0;
    virtual void disconnect() = 0;
};

// 目标对象:真实数据库连接
class RealDBConnection : public DBConnection {
private:
    std::string url;
    std::string username;
    std::string password;
    bool isConnected;

public:
    RealDBConnection(const std::string& u, const std::string& user, const std::string& pwd)
        : url(u), username(user), password(pwd), isConnected(false) {}
    
    void connect() override {
        std::cout << "Connecting to database: " << url 
                  << " as user: " << username << std::endl;
        // 实际连接逻辑...
        isConnected = true;
    }
    
    void executeQuery(const std::string& query) override {
        if (!isConnected) {
            std::cout << "Error: Not connected to database!" << std::endl;
            return;
        }
        std::cout << "Executing query: " << query << std::endl;
        // 实际查询逻辑...
    }
    
    void disconnect() override {
        if (isConnected) {
            std::cout << "Disconnecting from database: " << url << std::endl;
            // 实际断开连接逻辑...
            isConnected = false;
        }
    }
};

// 代理对象:数据库连接代理(保护代理)
class DBConnectionProxy : public DBConnection {
private:
    std::shared_ptr<DBConnection> realConnection;
    std::string username;
    std::string password;
    bool hasAdminPrivileges;
    mutable std::mutex mutex;

public:
    DBConnectionProxy(const std::string& url, const std::string& user, const std::string& pwd)
        : username(user), password(pwd), hasAdminPrivileges(false) {
        // 验证用户权限
        authenticate();
        // 延迟创建真实连接
        realConnection = nullptr;
    }
    
    void connect() override {
        std::lock_guard<std::mutex> lock(mutex);
        if (!realConnection) {
            realConnection = std::make_shared<RealDBConnection>(
                "jdbc:mysql://localhost:3306/mydb", username, password);
        }
        realConnection->connect();
    }
    
    void executeQuery(const std::string& query) override {
        // 权限检查
        if (!hasAdminPrivileges && isSensitiveQuery(query)) {
            std::cout << "Error: Insufficient privileges to execute this query!" << std::endl;
            return;
        }
        
        if (!realConnection) {
            connect();  // 延迟连接
        }
        
        realConnection->executeQuery(query);
    }
    
    void disconnect() override {
        if (realConnection) {
            realConnection->disconnect();
        }
    }
    
private:
    // 验证用户权限
    void authenticate() {
        // 模拟权限验证逻辑
        if (username == "admin" && password == "admin123") {
            hasAdminPrivileges = true;
            std::cout << "User has admin privileges" << std::endl;
        } else {
            std::cout << "User has normal privileges" << std::endl;
        }
    }
    
    // 检查是否为敏感查询
    bool isSensitiveQuery(const std::string& query) const {
        // 检测敏感操作(如DROP, DELETE等)
        return (query.find("DROP") != std::string::npos ||
                query.find("DELETE") != std::string::npos ||
                query.find("ALTER") != std::string::npos);
    }
};

// 客户端代码
int main() {
    // 创建普通用户连接
    DBConnection* normalUserConn = new DBConnectionProxy(
        "jdbc:mysql://localhost:3306/mydb", "user", "pass");
    
    // 执行普通查询
    normalUserConn->executeQuery("SELECT * FROM users");
    
    // 尝试执行敏感查询(会被拒绝)
    normalUserConn->executeQuery("DROP TABLE users");
    
    // 创建管理员连接
    DBConnection* adminConn = new DBConnectionProxy(
        "jdbc:mysql://localhost:3306/mydb", "admin", "admin123");
    
    // 执行敏感查询(管理员允许)
    adminConn->executeQuery("DROP TABLE temp_data");
    
    delete normalUserConn;
    delete adminConn;
    
    return 0;
}

十、实现注意事项

  1. 接口一致性
    • 确保代理对象和目标对象实现相同的接口,避免客户端代码修改。
  2. 内存管理
    • 使用智能指针管理目标对象的生命周期,避免内存泄漏。
  3. 线程安全
    • 在多线程环境中,对共享资源的访问需进行同步(如使用互斥锁)。
  4. 代理链
    • 可以创建多层代理,每个代理负责不同的功能(如日志、缓存、权限)。

代理模式是 C++ 中实现访问控制和增强功能的重要工具,通过引入代理对象,可以在不修改目标对象的前提下,灵活地添加额外功能,提高系统的可扩展性和安全性。


如果这篇文章对你有所帮助,渴望获得你的一个点赞!

在这里插入图片描述