C++软件设计模式之代理(Proxy)模式

Proxy 模式(代理模式)的意图、动机和适用场合

意图

Proxy 模式是一种结构型设计模式,它的主要意图是为其他对象提供一个代理以控制对这个对象的访问。通过引入代理对象,可以在不改变原有对象的基础上,增加额外的功能或限制对对象的访问。

动机
  1. 控制访问

    • 有时我们希望在访问对象之前或之后添加一些额外的处理逻辑,例如权限检查、日志记录、延迟初始化等。直接修改对象的代码可能会导致代码复杂性和维护成本的增加。使用代理模式可以在不改变对象本身的情况下,通过代理对象来实现这些功能。
  2. 性能优化

    • 对于一些资源消耗较大的对象,可以在需要时才进行初始化,并通过代理对象来管理这些对象的生命周期。这样可以避免在系统启动时就加载所有资源,从而提高性能。
  3. 远程代理

    • 在分布式系统中,客户端和服务器之间的通信可能涉及网络延迟和资源消耗。通过代理对象,可以将客户端的请求转发到服务器,并在服务器上执行相应的操作,返回结果给客户端。这样可以隐藏远程通信的复杂性。
  4. 智能引用

    • 对象的引用计数管理和自动资源释放可以通过代理对象来实现。例如,在智能指针中,代理对象可以负责管理对象的引用计数,并在引用计数为零时自动删除对象。
适用场合
  1. 延迟初始化

    • 当对象的创建和初始化过程较为复杂或资源消耗较大时,可以通过代理对象在需要时才进行初始化。例如,创建一个大型图片对象时,可以在第一次访问时才加载图片数据。
  2. 权限控制

    • 当需要在对象访问之前进行权限检查时,可以使用代理对象来实现。例如,一个文件对象在被读取或写入之前,代理对象可以检查当前用户是否有相应的权限。
  3. 日志记录

    • 当需要在对象的方法调用前后记录日志时,可以使用代理对象来实现。例如,一个数据库操作对象在执行查询或更新操作时,代理对象可以记录操作的时间和参数。
  4. 远程对象访问

    • 当需要访问远程对象时,可以使用代理对象来封装远程通信的细节。例如,一个网络服务的客户端可以通过代理对象来调用服务器上的方法。
  5. 缓存

    • 当需要缓存对象的结果以提高性能时,可以使用代理对象来实现。例如,一个昂贵的计算结果可以通过代理对象缓存起来,避免重复计算。
  6. 智能引用

    • 当需要管理对象的生命周期时,可以使用代理对象来实现引用计数管理。例如,std::shared_ptr 就是一个常见的代理模式的实现,用于管理对象的引用计数和自动资源释放。

示例代码:延迟初始化和权限控制

假设我们有一个大型的图像对象,我们希望在第一次访问时才加载图像数据,并且在访问之前进行权限检查。

1. 定义主题接口
cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

class Image {
public:
    virtual void draw() const = 0;
    virtual ~Image() {}
};

class RealImage : public Image {
private:
    std::string _filename;
    bool _isLoaded;
public:
    RealImage(const std::string& filename) : _filename(filename), _isLoaded(false) {}

    void draw() const override {
        if (!_isLoaded) {
            loadFromDisk();
        }
        std::cout << "绘制图像: " << _filename << std::endl;
    }

private:
    void loadFromDisk() const {
        std::cout << "加载图像: " << _filename << " 从磁盘" << std::endl;
        _isLoaded = true;
    }
};
2. 定义代理类
cpp 复制代码
class ImageProxy : public Image {
private:
    std::string _filename;
    mutable std::unique_ptr<RealImage> _realImage;
public:
    ImageProxy(const std::string& filename) : _filename(filename), _realImage(nullptr) {}

    void draw() const override {
        checkAccess();
        _realImage = std::make_unique<RealImage>(_filename);
        _realImage->draw();
    }

private:
    void checkAccess() const {
        std::cout << "检查权限: " << _filename << std::endl;
        // 这里可以添加实际的权限检查逻辑
        std::cout << "权限检查通过" << std::endl;
    }
};
3. 客户端代码
cpp 复制代码
int main() {
    ImageProxy proxy("large_image.jpg");

    // 第一次调用 draw,会进行权限检查和图像加载
    proxy.draw();

    // 第二次调用 draw,不再进行图像加载
    proxy.draw();

    return 0;
}

代码解释

  1. Image 接口

    • 定义了一个 draw 方法,用于绘制图像。
  2. RealImage 类

    • 实现了 Image 接口,具体的 draw 方法在绘制图像之前会检查图像是否已经加载,如果尚未加载,则从磁盘加载图像数据。
  3. ImageProxy 类

    • 也实现了 Image 接口,但它的 draw 方法在实际绘制图像之前会进行权限检查。
    • 如果权限检查通过,代理对象会创建一个 RealImage 对象并调用其 draw 方法。
    • checkAccess 方法用于进行权限检查,实际应用中可以添加具体的权限检查逻辑。
    • mutable 关键字用于允许 const 方法修改 _realImage,因为代理对象的 draw 方法可以在 const 环境下调用。
  4. 客户端代码

    • 创建 ImageProxy 对象,指定图像文件名。
    • 第一次调用 draw 方法时,代理对象会进行权限检查和图像加载。
    • 第二次调用 draw 方法时,图像已经加载,不会再进行加载操作。

权限控制

示例代码
cpp 复制代码
#include <iostream>
#include <string>

// 定义主题接口
class Image {
public:
    virtual void draw() const = 0;
    virtual ~Image() = default;
};

// 具体的图像实现
class RealImage : public Image {
private:
    std::string _filename;
public:
    RealImage(const std::string& filename) : _filename(filename) {}

    void draw() const override {
        loadFromDisk();
        std::cout << "绘制图像: " << _filename << std::endl;
    }

private:
    void loadFromDisk() const {
        std::cout << "加载图像: " << _filename << " 从磁盘" << std::endl;
    }
};

// 权限控制代理
class ImageProxy : public Image {
private:
    std::string _filename;
    mutable std::unique_ptr<RealImage> _realImage;
public:
    ImageProxy(const std::string& filename) : _filename(filename), _realImage(nullptr) {}

    void draw() const override {
        if (!checkAccess()) {
            std::cout << "权限检查未通过,无法绘制图像" << std::endl;
            return;
        }
        if (!_realImage) {
            _realImage = std::make_unique<RealImage>(_filename);
        }
        _realImage->draw();
    }

private:
    bool checkAccess() const {
        std::cout << "检查权限: " << _filename << std::endl;
        // 这里可以添加实际的权限检查逻辑
        std::cout << "权限检查通过" << std::endl;
        return true;
    }
};

int main() {
    ImageProxy proxy("large_image.jpg");

    // 第一次调用 draw,会进行权限检查和图像加载
    proxy.draw();

    // 第二次调用 draw,不再进行图像加载
    proxy.draw();

    return 0;
}

日志记录

示例代码
cpp 复制代码
#include <iostream>
#include <string>

// 定义主题接口
class Database {
public:
    virtual void execute(const std::string& query) = 0;
    virtual ~Database() = default;
};

// 具体的数据库实现
class RealDatabase : public Database {
public:
    void execute(const std::string& query) override {
        std::cout << "执行数据库查询: " << query << std::endl;
    }
};

// 日志记录代理
class DatabaseProxy : public Database {
private:
    std::unique_ptr<RealDatabase> _realDatabase;
public:
    DatabaseProxy() : _realDatabase(std::make_unique<RealDatabase>()) {}

    void execute(const std::string& query) override {
        logBefore(query);
        _realDatabase->execute(query);
        logAfter(query);
    }

private:
    void logBefore(const std::string& query) {
        std::cout << "日志: 正在执行查询: " << query << std::endl;
    }

    void logAfter(const std::string& query) {
        std::cout << "日志: 查询: " << query << " 执行完成" << std::endl;
    }
};

int main() {
    DatabaseProxy proxy;

    // 执行查询
    proxy.execute("SELECT * FROM users");

    return 0;
}

远程对象访问

示例代码
cpp 复制代码
#include <iostream>
#include <string>

// 定义主题接口
class Service {
public:
    virtual void processRequest(const std::string& request) = 0;
    virtual ~Service() = default;
};

// 具体的远程服务实现
class RemoteService : public Service {
public:
    void processRequest(const std::string& request) override {
        std::cout << "处理远程请求: " << request << std::endl;
    }
};

// 远程服务代理
class RemoteServiceProxy : public Service {
private:
    std::unique_ptr<RemoteService> _realService;
public:
    RemoteServiceProxy() : _realService(std::make_unique<RemoteService>()) {}

    void processRequest(const std::string& request) override {
        std::cout << "代理: 转发请求: " << request << " 到远程服务" << std::endl;
        _realService->processRequest(request);
        std::cout << "代理: 接收到远程服务的响应" << std::endl;
    }
};

int main() {
    RemoteServiceProxy proxy;

    // 处理请求
    proxy.processRequest("GET /users");

    return 0;
}

缓存

示例代码
cpp 复制代码
#include <iostream>
#include <string>
#include <unordered_map>

// 定义主题接口
class DataFetcher {
public:
    virtual std::string fetchData(const std::string& key) = 0;
    virtual ~DataFetcher() = default;
};

// 具体的数据获取实现
class RealDataFetcher : public DataFetcher {
public:
    std::string fetchData(const std::string& key) override {
        std::cout << "从数据库获取数据: " << key << std::endl;
        return "Data for " + key;
    }
};

// 缓存代理
class DataFetcherProxy : public DataFetcher {
private:
    std::unique_ptr<RealDataFetcher> _realDataFetcher;
    std::unordered_map<std::string, std::string> _cache;
public:
    DataFetcherProxy() : _realDataFetcher(std::make_unique<RealDataFetcher>()) {}

    std::string fetchData(const std::string& key) override {
        if (_cache.find(key) != _cache.end()) {
            std::cout << "从缓存中获取数据: " << key << std::endl;
            return _cache[key];
        }

        std::string data = _realDataFetcher->fetchData(key);
        _cache[key] = data;
        return data;
    }
};

int main() {
    DataFetcherProxy proxy;

    // 第一次请求,从数据库获取数据
    std::cout << proxy.fetchData("user1") << std::endl;

    // 第二次请求,从缓存中获取数据
    std::cout << proxy.fetchData("user1") << std::endl;

    return 0;
}

智能引用

示例代码
cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

// 定义主题接口
class Resource {
public:
    virtual void use() = 0;
    virtual ~Resource() = default;
};

// 具体的资源实现
class RealResource : public Resource {
public:
    RealResource(const std::string& name) : _name(name) {
        std::cout << "创建资源: " << _name << std::endl;
    }

    ~RealResource() override {
        std::cout << "销毁资源: " << _name << std::endl;
    }

    void use() override {
        std::cout << "使用资源: " << _name << std::endl;
    }

private:
    std::string _name;
};

// 智能引用代理
class ResourceProxy : public Resource {
private:
    std::string _name;
    mutable std::shared_ptr<RealResource> _resource;
public:
    ResourceProxy(const std::string& name) : _name(name), _resource(nullptr) {}

    void use() const override {
        if (!_resource) {
            _resource = std::make_shared<RealResource>(_name);
        }
        _resource->use();
    }
};

int main() {
    ResourceProxy proxy1("resource1");
    ResourceProxy proxy2("resource2");

    // 使用资源
    proxy1.use();
    proxy2.use();

    // 再次使用资源
    proxy1.use();
    proxy2.use();

    return 0;
}

代码解释

1. 权限控制
  • Image 接口 :定义了一个 draw 方法,用于绘制图像。
  • RealImage 类 :实现了 Image 接口,具体的 draw 方法会从磁盘加载图像数据并绘制。
  • ImageProxy 类 :实现了 Image 接口,但它的 draw 方法在实际绘制图像之前会进行权限检查。如果权限检查通过,则创建 RealImage 对象并调用其 draw 方法。
2. 日志记录
  • Database 接口 :定义了一个 execute 方法,用于执行数据库查询。
  • RealDatabase 类 :实现了 Database 接口,具体的 execute 方法会执行数据库查询。
  • DatabaseProxy 类 :实现了 Database 接口,但它的 execute 方法在执行查询前后会记录日志。
3. 远程对象访问
  • Service 接口 :定义了一个 processRequest 方法,用于处理请求。
  • RemoteService 类 :实现了 Service 接口,具体的 processRequest 方法会处理远程请求。
  • RemoteServiceProxy 类 :实现了 Service 接口,但它的 processRequest 方法会转发请求到远程服务,并在接收到响应后通知客户端。
4. 缓存
  • DataFetcher 接口 :定义了一个 fetchData 方法,用于获取数据。
  • RealDataFetcher 类 :实现了 DataFetcher 接口,具体的 fetchData 方法会从数据库获取数据。
  • DataFetcherProxy 类 :实现了 DataFetcher 接口,但它的 fetchData 方法会先检查缓存。如果缓存中有数据,则直接返回;否则,从数据库获取数据并缓存起来。
5. 智能引用
  • Resource 接口 :定义了一个 use 方法,用于使用资源。
  • RealResource 类 :实现了 Resource 接口,具体的 use 方法会使用资源,并在对象创建和销毁时输出相应的消息。
  • ResourceProxy 类 :实现了 Resource 接口,但它的 use 方法在实际使用资源之前会检查资源是否已经创建。如果尚未创建,则创建资源对象并使用;否则,直接使用已创建的资源对象。通过使用 std::shared_ptr,可以自动管理资源对象的生命周期。

通过这些示例代码,希望你能够更好地理解 Proxy 模式在不同场合下的应用。每种示例都展示了如何通过代理对象来增加额外的功能或控制对象的访问,同时保持主题接口的一致性。

保护代理模式(Protection Proxy)与装饰器模式(Decorator)的相似性与不同点

相似性
  1. 结构型设计模式

    • 保护代理模式和装饰器模式都是结构型设计模式,它们都通过在现有类的基础上添加一个新的类来扩展或控制现有类的功能。
  2. 继承或组合

    • 两种模式都通过继承现有的接口或类,或者通过组合的方式来实现扩展或控制功能。这种方式可以确保新的类与现有类在接口上保持一致,从而在客户端代码中可以无缝地替换现有类的实例。
  3. 动态性

    • 保护代理模式和装饰器模式都具有动态性,即可以在运行时决定是否添加或控制功能,而不需要在编译时对现有类进行修改。
不同点
  1. 设计意图

    • 保护代理模式:主要目的是控制对对象的访问。通过代理对象,可以在访问对象之前或之后添加权限检查、审计等逻辑,确保只有授权的客户端可以访问对象。
    • 装饰器模式:主要目的是在不改变对象接口的前提下,动态地给对象添加新的职责或行为。通过装饰器对象,可以在运行时动态地组合多个行为,从而实现功能的扩展。
  2. 功能扩展方式

    • 保护代理模式:通常只有一个代理对象,负责控制对单个对象的访问。代理对象的主要职责是权限检查和访问控制。
    • 装饰器模式:可以有多个装饰器对象,每个装饰器对象负责添加一个或多个新的职责。装饰器对象可以层层叠加,形成一个装饰器链,从而实现复杂的功能扩展。
  3. 对象状态

    • 保护代理模式:代理对象通常不会更改被代理对象的状态,只是控制访问。
    • 装饰器模式:装饰器对象可以更改被装饰对象的状态或行为,例如添加日志记录、缓存等。
  4. 代码复杂性

    • 保护代理模式:通常代码结构较为简单,因为只需要一个代理对象来控制访问。
    • 装饰器模式:代码结构可能更复杂,尤其是当需要添加多个装饰器时。每个装饰器都需要实现相同的接口,并且可以层层叠加。
示例代码对比
保护代理模式
cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

// 定义主题接口
class Image {
public:
    virtual void draw() const = 0;
    virtual ~Image() = default;
};

// 具体的图像实现
class RealImage : public Image {
private:
    std::string _filename;
public:
    RealImage(const std::string& filename) : _filename(filename) {}

    void draw() const override {
        loadFromDisk();
        std::cout << "绘制图像: " << _filename << std::endl;
    }

private:
    void loadFromDisk() const {
        std::cout << "加载图像: " << _filename << " 从磁盘" << std::endl;
    }
};

// 保护代理
class ImageProxy : public Image {
private:
    std::string _filename;
    mutable std::unique_ptr<RealImage> _realImage;
public:
    ImageProxy(const std::string& filename) : _filename(filename), _realImage(nullptr) {}

    void draw() const override {
        if (!checkAccess()) {
            std::cout << "权限检查未通过,无法绘制图像" << std::endl;
            return;
        }
        if (!_realImage) {
            _realImage = std::make_unique<RealImage>(_filename);
        }
        _realImage->draw();
    }

private:
    bool checkAccess() const {
        std::cout << "检查权限: " << _filename << std::endl;
        // 这里可以添加实际的权限检查逻辑
        std::cout << "权限检查通过" << std::endl;
        return true;
    }
};

int main() {
    ImageProxy proxy("large_image.jpg");

    // 第一次调用 draw,会进行权限检查和图像加载
    proxy.draw();

    // 第二次调用 draw,不再进行图像加载
    proxy.draw();

    return 0;
}
装饰器模式
cpp 复制代码
#include <iostream>
#include <string>

// 定义主题接口
class Image {
public:
    virtual void draw() const = 0;
    virtual ~Image() = default;
};

// 具体的图像实现
class RealImage : public Image {
private:
    std::string _filename;
public:
    RealImage(const std::string& filename) : _filename(filename) {}

    void draw() const override {
        loadFromDisk();
        std::cout << "绘制图像: " << _filename << std::endl;
    }

private:
    void loadFromDisk() const {
        std::cout << "加载图像: " << _filename << " 从磁盘" << std::endl;
    }
};

// 装饰器基类
class ImageDecorator : public Image {
protected:
    Image* _image;
public:
    ImageDecorator(Image* image) : _image(image) {}
    void draw() const override {
        _image->draw();
    }
};

// 日志记录装饰器
class LoggingImageDecorator : public ImageDecorator {
public:
    LoggingImageDecorator(Image* image) : ImageDecorator(image) {}

    void draw() const override {
        std::cout << "日志: 开始绘制图像" << std::endl;
        _image->draw();
        std::cout << "日志: 图像绘制完成" << std::endl;
    }
};

// 压缩装饰器
class CompressImageDecorator : public ImageDecorator {
public:
    CompressImageDecorator(Image* image) : ImageDecorator(image) {}

    void draw() const override {
        std::cout << "压缩图像数据" << std::endl;
        _image->draw();
    }
};

int main() {
    RealImage* realImage = new RealImage("large_image.jpg");

    // 使用多个装饰器
    Image* compressedImage = new CompressImageDecorator(realImage);
    Image* loggingImage = new LoggingImageDecorator(compressedImage);

    // 调用 draw 方法
    loggingImage->draw();

    // 释放内存
    delete loggingImage;
    delete compressedImage;
    delete realImage;

    return 0;
}

设计意图的区别

  1. 保护代理模式

    • 设计意图:控制对对象的访问。通过代理对象,可以在不改变对象本身的情况下,增加权限检查、审计等逻辑,确保只有授权的客户端可以访问对象。
    • 适用场景:适用于需要对对象访问进行控制的场景,例如资源访问控制、安全检查等。
    • 主要职责:权限检查、访问控制。
  2. 装饰器模式

    • 设计意图:在不改变对象接口的前提下,动态地给对象添加新的职责或行为。通过装饰器对象,可以在运行时动态地组合多个行为,从而实现功能的扩展。
    • 适用场景:适用于需要动态扩展对象功能的场景,例如添加日志记录、压缩、缓存等。
    • 主要职责:动态地添加职责或行为,功能扩展。

总结

  • 相似性

    • 两者都通过继承或组合的方式扩展或控制现有类的功能。
    • 两者都保持了现有类的接口一致性,可以无缝地替换现有类的实例。
  • 不同点

    • 保护代理模式主要关注访问控制,通过代理对象在访问对象之前或之后进行权限检查或审计。
    • 装饰器模式主要关注功能扩展,通过装饰器对象在运行时动态地给对象添加新的职责或行为,可以层层叠加形成装饰器链。
  • 设计意图

    • 保护代理模式:控制对对象的访问。
    • 装饰器模式:动态地扩展对象的功能。
相关推荐
编程之路,妙趣横生2 小时前
list模拟实现
c++
一只小bit4 小时前
数据结构之栈,队列,树
c语言·开发语言·数据结构·c++
沐泽Mu5 小时前
嵌入式学习-QT-Day05
开发语言·c++·qt·学习
渊渟岳6 小时前
掌握设计模式--装饰模式
设计模式
szuzhan.gy6 小时前
DS查找—二叉树平衡因子
数据结构·c++·算法
火云洞红孩儿6 小时前
基于AI IDE 打造快速化的游戏LUA脚本的生成系统
c++·人工智能·inscode·游戏引擎·lua·游戏开发·脚本系统
FeboReigns7 小时前
C++简明教程(4)(Hello World)
c语言·c++
FeboReigns7 小时前
C++简明教程(10)(初识类)
c语言·开发语言·c++
zh路西法8 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式
.Vcoistnt8 小时前
Codeforces Round 994 (Div. 2)(A-D)
数据结构·c++·算法·贪心算法·动态规划