首页 > 编程笔记 > C++笔记 阅读:15

代理模式详解(附带C++实例)

“代理”通常指代替别人行事的人或物。在软件设计中,代理模式体现了这样一个概念,一个代理对象代替另一个对象执行操作或控制对该对象的访问。

代理模式特别适用于情况复杂或需要额外控制层的场景,例如网络请求、大型图像处理或数据库访问等。

代理模式不仅可以控制访问,还可以负责实例化、加载、缓存等操作,从而提高系统整体的性能和安全性。

代理模式的实现流程

代理模式的核心思想是通过一个代理类来控制对另一个对象的访问。这个代理对象可以在客户端和真实对象之间起到中介的作用,从而可以在不修改真实对象代码的前提下增加额外的功能。

代理模式的实现流程如下图所示:


图 1 代理模式的实现流程

1) 定义接口

首先定义一个接口,该接口规定了真实对象和代理对象需要实现的方法。这保证了代理可以在任何时刻代替真实对象。

2) 创建真实对象类

真实对象类实现上述接口,定义实际的业务逻辑。

3) 创建代理类

代理类同样实现该接口,并持有一个对真实对象的引用。代理类通常在执行真实对象的方法前后执行额外的功能,如安全检查、缓存、延迟初始化等。

4) 使用代理

客户端不直接与真实对象交互,而是通过代理对象。这样,所有对真实对象的调用都会经过代理类。代理可以决定是否和何时将请求转发给真实对象,并在转发前后执行额外的操作。

5) 执行操作

代理在转发请求之前,可能会执行权限验证、日志记录、请求修改等预处理操作。然后,它将调用真实对象的方法。

6) 返回结果

真实对象完成请求后,结果会返回给代理对象。代理对象可以对结果进行后处理,然后将结果返回给客户端。

这种模式非常适合用于那些需要对象级别访问控制的场景,例如在需要控制和管理资源访问时,或者在对象的操作需要伴随额外行为时。代理模式也可以用来实现懒加载,即延迟对象的创建和初始化直到实际需要时,从而提高效率或减少系统资源的占用。

通过使用代理模式,可以使系统的结构更加清晰和简单,同时增加了对象的控制力,但代理模式也可能引入更多的类和对象,增加了系统的复杂性。

静态代理和动态代理

1) 静态代理

静态代理(static proxy)是在编译时已经完全确定下来的代理方式,意味着代理类和真实对象的关系在编译期间就已经建立,不会在运行时改变。

静态代理通常要求代理类和真实对象类实现相同的接口或继承相同的父类,代理类通过持有一个真实对象的引用来转发请求。

【实例展示】
class Subject {
public:
    virtual void request() = 0;
    virtual ~Subject() {}
};
class RealSubject : public Subject {
public:
    void request() override {
        std::cout << "RealSubject: Handling request." << std::endl;
    }
};
class Proxy : public Subject {
private:
    RealSubject* realSubject;
public:
    Proxy(RealSubject* realSubject) : realSubject(realSubject) {}
    void request() override {
        std::cout << "Proxy: Doing some work before passing request." << std::endl;
        realSubject->request();
        std::cout << "Proxy: Doing some work after passing request." << std::endl;
    }
    ~Proxy() {
        delete realSubject;
    }
};

2) 动态代理

动态代理(dynamic proxy)更为灵活,它允许在运行时创建代理对象,并动态地将请求转发给真实对象。这通常涉及更复杂的编程技术,如使用函数指针、Lambda 表达式或者第三方库(如 std::function)。

C++ 中,动态代理可能会用到库,如 Boost 或者 std::experimental::reflection(尚处于实验阶段的特性),来实现在运行时绑定方法。

动态代理的一个简单示例是使用 std::function 来存储和转发方法调用:
#include <functional>
#include <iostream>
class DynamicProxy {
private:
    std::function<void()> func;
public:
    DynamicProxy(const std::function<void()>& f) : func(f) {}
    void execute() {
        std::cout << "Proxy before executing." << std::endl;
        func();
        std::cout << "Proxy after executing." << std::endl;
    }
};
void realFunction() {
    std::cout << "Real Function is called." << std::endl;
}
int main() {
    DynamicProxy proxy(realFunction);
    proxy.execute();
    return 0;
}
这两种方式各有优劣,静态代理简单明了但缺乏灵活性,而动态代理虽然灵活但可能带来更复杂的实现和性能开销。

代理模式的应用场景

前面我们探讨了动态代理和静态代理的基本概念和实现方式,了解到每种代理在某些情境下的适用性和优势。然而,在实际开发中,选择合适的代理模式往往需要基于具体的业务需求和场景。

接下来,我们将深入探讨几种常见的具体需求情况,分析在这些情况下如何有效地选择和实现代理模式,以确保既能满足功能需求,又能提供优化的性能和资源管理。

1) 权限控制与安全性管理

首先,让我们考虑一种需要控制对象访问权限的场景。假设正在开发一个企业管理系统,其中包含敏感的用户数据和业务信息。在这样的系统中,不同级别的用户(例如普通员工、部门经理和系统管理员)应有不同级别的数据访问权限。那么如何在不修改现有业务逻辑的前提下,实现这样的访问控制呢?

这里,保护代理模式提供了一个优雅的解决方案。通过在真实对象之前设置一个保护代理,系统可以在代理层进行安全性检查,根据用户的身份和权限决定是否允许访问特定的数据。

代理模式不仅保护了对象,避免了直接的不恰当访问,还帮助维持了业务逻辑和数据访问逻辑的分离。

例如,实现一个 DocumentAccessProxy 类,它可以代理对 Document 类的访问。在这个代理类中,每次访问都会先检查用户的权限:
class Document {
public:
    void displayDocument() const {
        std::cout << "Displaying document content." << std::endl;
    }
};
class DocumentAccessProxy {
private:
    Document* document;
    User* user;
public:
    DocumentAccessProxy(Document* doc, User* usr) : document(doc), user(usr) {}
    void displayDocument() {
       if (user->hasPermission("READ")) {
           document->displayDocument();
       } else {
           std::cout << "Access denied. Insufficient permissions." << std::endl;
       }
    }
};
在这个示例中,代理类 DocumentAccessProxy 承担了检查权限的责任,而 Document 类则专注于其核心功能,即展示文档内容。这种分离确保了系统的可扩展性和可维护性。

2) 资源优化与延迟初始化

接下来,让我们探索另一个常见需求——延迟初始化。

假设正在开发一个图形密集型的应用程序,如视频游戏或设计软件,其中包含大量的图像资源。如果在程序启动时就加载所有图像,会显著增加启动时间并消耗大量内存。然而,不是所有的图像都会在每个会话中被使用。那么如何更有效地管理这些资源呢?

这里,虚拟代理模式提供了一个理想的解决方案。虚拟代理允许我们延迟实际对象的创建直到真正需要它时,从而优化资源的使用和应用程序的性能。代理对象在内部管理对象的初始化过程,并在必要时才创建或加载真实对象。

考虑以下实现方式,创建一个 Image 接口和一个 ProxyImage 类,后者在用户首次需要图像时才加载它:
class Image {
public:
    virtual void display() = 0;
    virtual ~Image() {}
};
class RealImage : public Image {
private:
    std::string filename;
public:
    RealImage(const std::string& fname) : filename(fname) {
        loadFromDisk();
    }
    void display() override {
        std::cout << "Displaying " << filename << std::endl;
    }
    void loadFromDisk() {
        std::cout << "Loading " << filename << std::endl;
    }
};
class ProxyImage : public Image {
private:
    RealImage* realImage;
    std::string filename;
public:
    ProxyImage(const std::string& fname) : filename(fname), realImage(nullptr) {}
    void display() {
        if (!realImage) {
            realImage = new RealImage(filename);
        }
        realImage->display();
    }
    ~ProxyImage() {
        delete realImage;
    }
};
在此例中,ProxyImage 在首次被请求展示时才加载图片,之后的调用直接使用已加载的图片。这种方式显著减少了程序的初始加载时间和运行时内存需求。

3) 异步处理与系统响应性

接下来,让我们考虑一个高流量的客户服务中心系统,该系统需要处理大量的用户查询和数据请求。在高峰时段,同步处理所有请求可能会导致显著的响应延迟。为了提高响应速度和系统的整体效率,我们可以引入一个异步处理模型。

在这个场景中,一个智能引用代理或保护代理非常有用。例如,代理可以管理一个任务队列,所有的请求首先被代理接收并放入队列。代理随后根据系统当前的负载情况,异步地处理这些请求。这种方式不仅平衡了负载,还提高了用户的感知性能,因为他们不需要等待每个请求被同步处理。

具体实现上,我们可以使用 C++ 中的多线程和异步编程技术来设计这样一个代理。
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>

class AsyncProxy {
private:
    std::queue<std::function<void()>> tasks;
    std::mutex mtx;
    std::condition_variable cv;
    bool stopped = false;

    void worker() {
        while (true) {
            std::function<void()> task;
            {
                std::unique_lock<std::mutex> lock(mtx);
                cv.wait(lock, [this] { return tasks.empty() || stopped; });
                if (stopped && tasks.empty()) break;
                task = std::move(tasks.front());
                tasks.pop();
            }
            task(); // 执行任务
        }

public:
    AsyncProxy() {
        std::thread(&AsyncProxy::worker, this).detach();
    }

    ~AsyncProxy() {
        {
            std::lock_guard<std::mutex> lock(mtx);
            stopped = true;
        }
        cv.notify_all();
    }

    void addTask(const std::function<void()>& task) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            tasks.push(task);
        }
        cv.notify_one();
    }

    void handleRequest() {
        std::cout << "Handling request asynchronously." << std::endl;
    }

    int main() {
        AsyncProxy proxy;
        proxy.addTask(handleRequest);
        proxy.addTask(handleRequest);
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟等待更多任务
        return 0;
    }
}
通过引入一个异步代理,我们可以有效地管理并发请求,优化系统的响应时间,并减轻高流量条件下的压力。这种设计不仅提高了系统的可扩展性,还改善了用户体验,显示了代理模式在现代软件架构中的广泛应用性和灵活性。

至此,我们详细探讨了代理模式在权限控制与安全性管理、资源优化与延迟初始化、异步处理与系统响应性这三个具体场景中的应用。这些例子展示了代理模式如何根据不同的业务需求提供定制化的解决方案,从而增强应用程序的功能性和效率。

然而,代理模式的潜力远不止于此。在现代软件开发中,还有诸多场景可能会从代理模式中受益,例如缓存代理用于提高数据访问速度、日志记录代理用于审计和监控系统活动,甚至复杂的事务处理代理用于确保数据的一致性和恢复。每种用途都有其独特的实现方式和优势。

相关文章