结构型设计模式——代理模式

文章目录

代理模式

代理,以其最一般的形式,是一个类,用作其他东西的接口。**代理是一个包装器或代理对象,客户端正在调用它来访问幕后的真实服务对象。代理的使用可以简单地转发到真实对象,或者可以提供额外的逻辑。**在代理中,可以提供额外的功能,例如,当对真实对象的操作是资源密集型时缓存,或者在调用对真实对象的操作之前检查前提条件。

给某一对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

控制对于某个对象的访问必要性:

比如有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。

  • 使用延迟加载 :可以使用延迟加载的方式来解决,当需要用到的时候再初始化对象。
    • 如果这个对象的初始化过程非常麻烦,在不同的客户端都要去做这个初始化,操作非常不方便还会出现很多重复代码。
    • 理想状态将控制对象的代码植入到原始对象,但是如原始对象是第三方库就无能为力了。
  • 使用代理对象:新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。

结构

代理模式包含如下三个角色:

  • Service Interface (服务接口):声明了服务接口(真实角色和代理角色共有接口)。 这样一来在任何使用真实角色的地方都可以使用代理角色,客户端通常需要针对服务接口进行编程。
  • Service(服务):它定义了代理角色所代表的真实对象,在真实角色中实现了真实的业务操作,客户端可以通过代理角色间接调用真实角色中定义的操作。
  • Proxy (代理):包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。 通常情况下, 代理会对其服务对象的整个生命周期进行管理。
  • Client(客户端):能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。

与其他模式:

  • 适配器模式 能为被封装对象提供不同的接口, 代理模式 能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。
  • 外观模式代理模式 的相似之处在于++它们都缓存了一个复杂实体并自行对其进行初始化++。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。
  • 装饰模式代理模式 有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期 , 而装饰的生成则总是由客户端进行控制

实现

cpp 复制代码
// 抽象通信类
class Communication
{
public:
    virtual void communicate() = 0; // 通话
    virtual ~Communication() {}
};
cpp 复制代码
// 讲话的人
class Speaker : public Communication
{
public:
    void communicate() override
    {
        cout << "开始说话..." << endl;
        cout << "通话时发生了一些列的表情变化..." << endl;
    }
};
cpp 复制代码
// 电话虫
class DenDenMushi : public Communication
{
public:
    DenDenMushi()
    {
        m_isStart = true;
        m_speaker = new Speaker;
    }
    ~DenDenMushi()
    {
        if (m_speaker != nullptr)
        {
            delete m_speaker;
        }
    }
    // 判断是否已经开始通话了
    bool isStart()
    {
        return m_isStart;
    }
    void communicate() override
    {
        if (isStart())
        {
            // 得到通话者语言和表情信息, 并加以模仿
            cout << "电话虫开始实时模仿通话者的语言和表情..." << endl;
            m_speaker->communicate();
       }
    }
private:
    bool m_isStart = false;
    Speaker* m_speaker = nullptr;
};
cpp 复制代码
int main()
{
    // 直接交流
    Communication* comm = new Speaker;
    comm->communicate();
    delete comm;
    cout << "===================================" << endl;
    // 使用电话虫
    comm = new DenDenMushi;
    comm->communicate();
    delete comm;

    return 0;
}

远程代理

远程代理(Remote Proxy),又称为大使(Ambassador),是一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。

远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>
#include <random>

/**
 * 1. 抽象接口 (Subject)
 * 定义了远程服务和代理的共同接口。
 */
class RemoteServiceInterface {
public:
    virtual ~RemoteServiceInterface() = default;
    virtual long doRemoteFunction(int val) = 0;
};

/**
 * 2. 真实服务 (Real Subject) - RemoteService
 * 运行在"远程"服务器上的实际业务逻辑。
 */
class RemoteService : public RemoteServiceInterface {
private:
    // 采用单例模式或简单构造,图中显示为私有构造
    RemoteService() {}

public:
    static RemoteService& getRemoteService() {
        static RemoteService instance;
        return instance;
    }

    // 实现业务逻辑
    long doRemoteFunction(int val) override {
        // 1. 通过随机数产生一个休眠数字
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(100, 2000); // 模拟 100ms 到 2000ms 的延迟
        int sleepTime = dis(gen);

        // 2. 休眠
        std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));

        // 3. 休眠数字 > 某个值(如 1500ms)返回 -1 表示失败,否则返回处理后的值
        if (sleepTime > 1500) {
            return -1; 
        }
        return static_cast<long>(val) * 10; // 假设处理逻辑是乘以 10
    }
};

/**
 * 3. 代理类 (Proxy) - ServiceAmbassador
 * 负责控制对远程服务的访问,包含延迟检查和重试机制。
 */
class ServiceAmbassador : public RemoteServiceInterface {
private:
    const int RETRIES = 3;           // 最大重试次数
    const long DELAY_MS = 500;       // 重试间隔延迟

    // 辅助方法:延迟检查中计算请求时间差并返回结果
    long checkLatency(int val) {
        auto start = std::chrono::high_resolution_clock::now();
        
        // 调用远程服务
        long result = RemoteService::getRemoteService().doRemoteFunction(val);
        
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
        
        std::cout << "[Log] 请求耗时: " << duration.count() << "ms" << std::endl;
        return result;
    }

public:
    // 接口实现中模拟使用失败重试
    long doRemoteFunction(int val) override {
        int attempts = 0;
        while (attempts < RETRIES) {
            std::cout << "[Proxy] 正在尝试调用远程服务 (尝试 " << attempts + 1 << ")..." << std::endl;
            
            long result = checkLatency(val);
            
            if (result != -1) {
                return result; // 成功则返回
            }

            std::cout << "[Proxy] 调用失败,等待重试..." << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_MS));
            attempts++;
        }

        std::cerr << "[Proxy] 已达到最大重试次数,远程服务不可用。" << std::endl;
        return -1;
    }
};

/**
 * 4. 客户端 (Client)
 */
int main() {
    // 客户端通过代理(大使)访问服务
    ServiceAmbassador* proxy = new ServiceAmbassador();
    
    int testValue = 42;
    std::cout << "--- 客户端发起远程请求 ---" << std::endl;
    
    long finalResult = proxy->doRemoteFunction(testValue);
    
    if (finalResult != -1) {
        std::cout << "最终结果: " << finalResult << std::endl;
    } else {
        std::cout << "请求最终失败。" << std::endl;
    }

    delete proxy;
    return 0;
}

虚拟代理

**虚拟代理(Virtual Proxy)**也是一种常见的代理模式,对于一些占用系统资源较多的或者加载时间长的对象,可以给系统提供一个虚拟代理。

  • 如果加载时间过长,可以在虚拟代理对象中使用多线程执行异步加载;
  • 如果系统资源过多,可以在虚拟代理对象中执行延迟加载,使用完成后也可以做资源释放。
cpp 复制代码
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <memory>

// --- 前向声明 ---
class Container;

/**
 * 1. 抽象接口 (Subject) - Icon
 * 定义了代理类和真实实体类的共同接口
 */
class Icon {
public:
    virtual ~Icon() = default;
    virtual int getWidth() = 0;
    virtual int getHeight() = 0;
    virtual void paintIcon(Container* container) = 0;
};

/**
 * 2. 容器类 (Context) - Container
 * 用于接收并显示 Icon 的内容
 */
class Container {
public:
    void showContent(int w, int h, std::string content) {
        std::cout << "[容器显示] 尺寸: " << w << "x" << h 
                  << " | 内容: " << content << std::endl;
    }
};

/**
 * 3. 真实实体类 (Real Subject) - ImageIcon
 * 负责加载和渲染真正的图像,实例化代价较高
 */
class ImageIcon : public Icon {
private:
    int width;
    int height;
    std::string imageUrl;

public:
    ImageIcon(std::string url) : imageUrl(url) {
        // 模拟高昂的图像加载开销
        std::cout << "--- 正在从网络加载图像: " << imageUrl << " ---" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟加载延迟
        width = 1920;
        height = 1080;
        std::cout << "--- 图像加载完成 ---" << std::endl;
    }

    int getWidth() override { return width; }
    int getHeight() override { return height; }
    
    void paintIcon(Container* container) override {
        container->showContent(width, height, "高清图像数据 [URL: " + imageUrl + "]");
    }
};

/**
 * 4. 虚拟代理类 (Proxy) - ImageProxy
 * 控制对 ImageIcon 的访问,直到真正需要时才创建它
 */
class ImageProxy : public Icon {
private:
    std::unique_ptr<ImageIcon> realIcon; // 指向真实实体的指针
    std::string url;
    bool isRetrieving = false;

public:
    ImageProxy(std::string url) : url(url), realIcon(nullptr) {}

    // 在图像未加载前返回预设值
    int getWidth() override {
        if (realIcon) return realIcon->getWidth();
        return 800; // 默认宽度
    }

    int getHeight() override {
        if (realIcon) return realIcon->getHeight();
        return 600; // 默认高度
    }

    // 核心逻辑:按需加载
    void paintIcon(Container* container) override {
        if (realIcon) {
            // 如果已加载,直接使用真实对象
            realIcon->paintIcon(container);
        } else {
            // 如果未加载,显示占位信息并开始实例化真实对象
            std::cout << "[代理] 图像尚未就绪,显示占位符..." << std::endl;
            container->showContent(getWidth(), getHeight(), "加载中,请稍候...");
            
            if (!isRetrieving) {
                isRetrieving = true;
                // 实例化真实实体(在实际应用中通常会放在后台线程执行)
                realIcon = std::make_unique<ImageIcon>(url);
            }
        }
    }
};

/**
 * 5. 客户端/应用 (Client) - App
 */
int main() {
    Container* myContainer = new Container();
    
    // 创建代理,此时并不会立即从网络下载图片
    std::cout << "1. 创建图像代理..." << std::endl;
    Icon* imageProxy = new ImageProxy("http://example.com/large_image.jpg");

    // 第一次调用 paintIcon,显示占位符并触发加载
    std::cout << "\n2. 第一次尝试显示图像:" << std::endl;
    imageProxy->paintIcon(myContainer);

    // 第二次调用 paintIcon,此时图片已加载,显示真实内容
    std::cout << "\n3. 第二次尝试显示图像:" << std::endl;
    imageProxy->paintIcon(myContainer);

    delete imageProxy;
    delete myContainer;
    
    return 0;
}

Java 动态代理

代理模式分类
  • 静态代理 (Static Proxy):要求代理类和真实类是预先定义好的,编译后都会生成对应的 class 文件。
  • 动态代理 (Dynamic Proxy) :系统在运行时根据需要动态创建代理类。
    • 优点:同一个代理类能够代理多个不同的真实类,并且可以代理不同的方法。
    • 应用领域:广泛应用于事务管理、AOP(面向切面编程)等领域。
    • 技术支持:Java 从 JDK 1.3 开始提供支持,主要使用反射包(java.lang.reflect`)下的类。
Proxy 类

Proxy 类提供了用于手动创建动态代理类和实例对象的方法,它是所有创建出的代理类的父类。其常用方法包括:

  • getProxyClass:用于返回一个 Class` 类型的代理类。
    • 参数需提供类加载器和指定的代理接口数组(需与真实类的接口一致)。
  • newProxyInstance:用于返回一个动态代理类实例。该方法包含三个参数:
    1. 第一个参数:代理类的类加载器。
    2. 第二个参数:代理类所实现的接口列表。
    3. 第三个参数:指派的调用处理程序类。
InvocationHandler 接口

该接口是代理实例的调用处理程序的公共父接口。

  • 核心方法invoke:用于处理对代理类实例的方法调用并返回结果。当代理实例的方法被调用时,会自动触发此方法。
  • invoke`方法参数
    1. 第一个参数:代理类的实例。
    2. 第二个参数:需要代理的方法。
    3. 第三个参数:代理方法的参数数组。
运行机制

动态代理类需要在运行时指定被代理的真实类的接口。客户端在调用动态代理对象的方法时,请求会自动转发给 InvocationHandler 对象的 invoke方法,由其实现对请求的统一处理。

实现
cpp 复制代码
// 用户操作接口
interface AbstractUserDAO {
    boolean updateById(String id);
}

// 文档操作接口
interface AbstractDocumentDAO {
    boolean deleteById(String id);
}
cpp 复制代码
// 真实的用户数据访问对象
class UserDAO implements AbstractUserDAO {
    @Override
    public boolean updateById(String id) {
        System.out.println("【数据库操作】正在更新用户 ID 为 " + id + " 的记录...");
        return true;
    }
}

// 真实的文档数据访问对象
class DocumentDAO implements AbstractDocumentDAO {
    @Override
    public boolean deleteById(String id) {
        System.out.println("【数据库操作】正在删除文档 ID 为 " + id + " 的记录...");
        return true;
    }
}
cpp 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Calendar;

/**
 * 动态代理日志处理器
 * 实现了 java.lang.reflect.InvocationHandler
 */
class LogHandler implements InvocationHandler {
    private Object proxyObj; // 真实的业务对象

    public LogHandler(Object proxyObj) {
        this.proxyObj = proxyObj;
    }

    // 私有辅助方法:获取日志前缀(如当前时间)
    private String getLogPrefixStr() {
        Calendar calendar = Calendar.getInstance();
        return "[LOG - " + calendar.getTime().toString() + "] ";
    }

    // 预处理逻辑:方法调用前执行
    private void beforeInvoke(Object id) {
        System.out.println(getLogPrefixStr() + "准备执行操作,目标 ID: " + id);
    }

    // 后处理逻辑:方法调用后执行
    private void afterInvoke(Object res, Object id) {
        System.out.println(getLogPrefixStr() + "操作执行完毕,ID: " + id + ",结果: " + res);
    }

    /**
     * 实现 invoke 方法
     * 当代理对象的方法被调用时,实际会跳转到此方法执行
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取方法参数中的 ID (对应 UML 中的 id)
        Object id = (args != null && args.length > 0) ? args[0] : "N/A";

        // 1. 调用前置通知
        beforeInvoke(id);

        // 2. 通过反射执行目标对象的真实方法
        Object result = method.invoke(proxyObj, args);

        // 3. 调用后置通知
        afterInvoke(result, id);

        return result;
    }
}
cpp 复制代码
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // --- 1. 为 UserDAO 创建动态代理 ---
        AbstractUserDAO realUserDAO = new UserDAO();
        LogHandler userHandler = new LogHandler(realUserDAO);

        // 使用 Proxy.newProxyInstance 动态生成代理类实例
        AbstractUserDAO userProxy = (AbstractUserDAO) Proxy.newProxyInstance(
                AbstractUserDAO.class.getClassLoader(),
                new Class[]{AbstractUserDAO.class},
                userHandler
        );

        userProxy.updateById("U888"); // 执行时会自动插入日志


        System.out.println("--------------------------------------------------");


        // --- 2. 为 DocumentDAO 创建动态代理 ---
        AbstractDocumentDAO realDocDAO = new DocumentDAO();
        LogHandler docHandler = new LogHandler(realDocDAO);

        AbstractDocumentDAO docProxy = (AbstractDocumentDAO) Proxy.newProxyInstance(
                AbstractDocumentDAO.class.getClassLoader(),
                new Class[]{AbstractDocumentDAO.class},
                docHandler
        );

        docProxy.deleteById("D999"); // 执行时会自动插入日志
    }
}

其他代理模式

  • 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

特点

主要优点

  • 代理模式的共同优点如下
    • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
    • 客户端可以针对抽象角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
  • 不同类型的代理模式也具有独特的优点 ,例如:
    • 远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
    • 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
    • 缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
    • 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。

主要缺点

  • 由于在客户端和真实类之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
  • 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。

适用环境

📢不同类型的代理模式有不同的优缺点,它们应用于不同的场合。

  • 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
  • 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理。
  • 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
  • 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
  • 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理
相关推荐
万邦科技Lafite1 小时前
京东商品详情 API 接口全面讲解
java·数据库·redis·api·电商开放平台
故事和你911 小时前
洛谷-【图论2-1】树2
开发语言·数据结构·c++·算法·动态规划·图论
折哥的程序人生 · 物流技术专研1 小时前
Java面试85题图解版 · 全系列总目录
java·开发语言·后端·面试·职场和发展
武子康1 小时前
Java-01 深入浅出 MyBatis 入门与核心原理:半自动 ORM 框架详解
java·后端·mybatis
木易 士心1 小时前
Java 跳出多层循环
java·开发语言·后端
数电发票API1 小时前
数电发票接口对接流程详解:从认证到冲红的完整指南
java
kyle~1 小时前
C++---段错误(SIGSEGV)
linux·运维·c++·机器人
乐观勇敢坚强的老彭1 小时前
day515C++信奥循环嵌套强化03
开发语言·c++
杜子不疼.1 小时前
【C++ AI 大模型接入 SDK】 - 环境搭建
开发语言·数据库·c++