代理设计模式:从底层原理到源代码 详解

news2025/4/23 12:17:34

        代理设计模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对目标对象的访问。代理对象充当客户端和目标对象之间的中介,允许在不修改目标对象的情况下添加额外的功能(如权限控制、日志记录、延迟加载等)。以下将从底层原理到源代码层面,逐步为非专业人士详细解释代理设计模式的每个方面。


一、代理设计模式的通俗概念

1. 什么是代理?

        想象你在网上购物,想买一件衣服,但你没有时间直接去实体店。于是,你请了一个朋友(代理)帮你去店里挑衣服、付款并带回来。这个朋友就是“代理”,他代替你完成了与商店的交互。你(客户端)只需要告诉代理你的需求(比如颜色、尺码),代理会帮你处理一切细节。

 在程序设计中,代理模式也是类似的:

         - 客户端:发出请求的一方(比如你)。

         - 代理对象:代替客户端与目标对象交互的对象(比如你的朋友)。

         - 目标对象:真正完成工作的对象(比如商店里的衣服)。

        代理对象可以在客户端和目标对象之间添加额外的逻辑,比如检查你是否有足够的钱(权限控制)、记录你买了什么(日志记录),或者延迟去商店直到你确认要买(延迟加载)。

2. 为什么需要代理?

        代理模式解决的问题是:在不直接修改目标对象的情况下,控制访问或增强功能

        常见场景包括:

                 - 权限控制:只有特定用户可以访问目标对象。

                 - 延迟加载:只有在需要时才加载目标对象(比如加载大文件)。

                 - 日志记录:记录目标对象被调用的时间和参数。

                 - 远程访问:代理隐藏了目标对象在远程服务器上的细节。

3. 代理模式的本质

        代理模式的核心是封装和控制。代理对象封装了对目标对象的访问,客户端通过代理间接调用目标对象的方法。代理可以在调用前后添加额外的逻辑,但客户端无需关心这些细节。


二、代理设计模式的底层原理

代理模式的实现基于面向对象编程的以下关键概念:

 1. 接口/抽象类:代理对象和目标对象通常实现同一个接口或继承同一个抽象类,确保它们有相同的方法签名,客户端可以无缝切换。

 2. 组合/委托:代理对象通常持有一个目标对象的引用,通过委托(调用目标对象的方法)完成实际工作。

 3. 拦截和增强:代理对象在调用目标对象的方法前后插入额外的逻辑,控制访问或增强功能。

工作流程(以买衣服为例)

        你(客户端)告诉代理:“我要买一件红色的衣服。”

        代理检查你的请求(比如确认你有足够的钱)。

        代理将请求转发给商店(目标对象),商店提供衣服。

        代理可能记录日志(“你买了一件红色衣服”)。

        代理将衣服返回给你。

        在代码中,这个流程表现为:

                 - 客户端调用代理对象的方法。

                 - 代理对象执行前置逻辑(如检查权限)。

                 - 代理对象调用目标对象的方法。

                 - 代理对象执行后置逻辑(如记录日志)。

                 - 代理对象返回结果给客户端。


三、代理模式的类型

代理模式根据用途分为几种常见类型,理解这些类型有助于选择合适的实现方式:

 1. 虚拟代理(Virtual Proxy):延迟加载目标对象,适合目标对象创建成本高的情况(如加载大图片)。

 2. 保护代理(Protection Proxy):控制对目标对象的访问,通常用于权限管理。

 3. 远程代理(Remote Proxy):隐藏目标对象位于远程服务器的细节,客户端感觉像在本地调用。

 4. 智能代理(Smart Proxy):在调用目标对象时添加额外功能,如日志、计数等。

本文将以保护代理为例,详细讲解其实现,因为它简单且能清晰展示代理模式的原理。


四、代理模式的详细实现

        以下通过一个具体的例子,用 Java 语言从头实现一个保护代理,逐步解释每部分代码的原理。假设我们有一个文件访问系统,只有管理员可以删除文件,普通用户只能读取文件。

静态代理

1. 定义接口(统一代理和目标对象的契约)

        我们需要一个接口,定义文件操作的行为。代理和目标对象都实现这个接口,确保客户端可以用一致的方式调用它们。

 public interface FileAccess {
        void readFile(String fileName);
        void deleteFile(String fileName);
}

解释

 - FileAccess 接口定义了两个方法:readFile(读取文件)和deleteFile(删除文件)。

 - 代理和目标对象都实现这个接口,客户端通过接口调用方法,无需关心背后是代理还是目标对象。

 - 这就像你告诉代理“我要买衣服”,代理和商店都理解“买衣服”这个指令。

2. 实现目标对象(实际干活的类)

目标对象是真正执行文件操作的类,比如实际访问文件系统。

public class RealFileAccess implements FileAccess {
    @Override
    public void readFile(String fileName) { 
        System.out.println("读取文件: " + fileName); 
    }

    @Override
    public void deleteFile(String fileName) {
        System.out.println("删除文件: " + fileName);
    }

}

解释

 - RealFileAccess 是目标对象,实现了 FileAccess 接口。

 - readFile 和 deleteFile 方法模拟文件操作,实际中可能涉及文件系统调用。

 - 这个类就像商店,负责实际提供衣服(执行核心逻辑)。

3. 实现代理对象(控制访问)

代理对象也实现 FileAccess 接口,但它会检查权限,并在调用目标对象之前添加控制逻辑。

public class FileAccessProxy implements FileAccess { 
    private RealFileAccess realFileAccess; 
    private String userRole;

    public FileAccessProxy(String userRole) {
        this.userRole = userRole;
        this.realFileAccess = new RealFileAccess(); // 持有目标对象引用
    }

    @Override
    public void readFile(String fileName) {
        System.out.println("Proxy: 记录读请求 " + fileName);
        realFileAccess.readFile(fileName); // 委托给目标对象
    }

    @Override
    public void deleteFile(String fileName) {
        if (userRole.equals("admin")) {
            System.out.println("Proxy: 有权删除文件: " + fileName);
            realFileAccess.deleteFile(fileName); // 委托给目标对象
        } else {
            System.out.println("Proxy: 权限不足,只有管理员才能删除文件.");
        }
    }

}

解释

 - 构造函数:FileAccessProxy 接受 userRole(用户角色,如 “admin” 或 “user”),并创建目标对象 RealFileAccess。

- 持目标对象引用:代理通过 realFileAccess 字段持有目标对象的引用,用于委托调用。

 - readFile 方法:代理直接调用目标对象的 readFile,并添加日志记录(前置逻辑)。

 - deleteFile 方法:代理检查用户角色,只有管理员(userRole 为 “admin”)可以删除文件,否则拒绝访问。

 - 这就像你的朋友(代理)在去商店前检查你是否有钱(权限),然后才帮你买衣服。

4. 客户端代码(使用代理)

客户端通过代理对象访问文件系统,无需直接接触目标对象。

public class Main {
    public static void main(String[] args) {
        // 普通用户
        FileAccess userAccess = new FileAccessProxy(“user”);
        userAccess.readFile(“data.txt”);
        userAccess.deleteFile(“data.txt”);
        System.out.println("---");

        // 管理员
        FileAccess adminAccess = new FileAccessProxy("admin");
        adminAccess.readFile("data.txt");
        adminAccess.deleteFile("data.txt");
    }
}

输出

Proxy: 记录读请求文件 data.txt
读取文件: data.txt
Proxy: 权限不足,只有管理员才能删除文件.
---
Proxy: 记录读请求的文件:data.txt
读取文件: data.txt
Proxy: 有权删除文件: data.txt
删除文件: data.txt

解释

 - 客户端创建两个代理对象:一个普通用户(user),一个管理员(admin)。

 - 普通用户可以读取文件,但删除文件时被拒绝。

 - 管理员可以读取和删除文件。

 - 客户端只与代理交互(FileAccess 接口),无需知道目标对象或权限检查的细节。

动态代理(Dynamic Proxy)

        动态代理是一种在运行时动态生成代理对象的技术,主要用于在不修改原始类代码的情况下,增强或控制目标对象的行为。其核心思想是通过 反射 和 接口 在运行时生成代理类,实现对目标方法的拦截和增强。

下面以租房为例:

现有租房的接口Rent

房东类Host 实现Rent接口

代理类RentHandler实现InvocationHandler接口

1. 动态代理的核心组件

组件作用关键类/接口
抽象接口定义代理类和真实类共同的行为Rent(租房接口)
真实对象实际执行业务逻辑的类Host(房东类)
调用处理器拦截方法调用并增强逻辑InvocationHandler
动态代理类运行时生成的代理对象Proxy.newProxyInstance()

2. 动态代理的设计步骤

(1) 定义抽象接口

代理类和真实类必须实现相同的接口,确保方法调用的兼容性。

public interface Rent {
    void rent();
    int getPrice();
}
(2) 实现真实对象(被代理类)
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }

    @Override
    public int getPrice() {
        return 5000;
    }
}
(3) 实现调用处理器(InvocationHandler

负责拦截方法调用,并插入增强逻辑(如日志、权限检查、事务管理等)。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RentHandler implements InvocationHandler {
    private final Object target; // 被代理的真实对象(如 Host)

    public RentHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强(如权限检查)
        System.out.println("[代理] 正在检查租客信用...");

        // 调用真实对象的方法
        Object result = method.invoke(target, args);
​​​​​​​
        // 后置增强(如日志记录)
        System.out.println("[代理] 租房完成,签订合同");
        
        // 可修改返回值(如砍价)
        if ("getPrice".equals(method.getName())) {
            return (int) result - 500; // 代理砍价 500 元
        }
        return result;
    }
}
(4) 动态生成代理对象

使用 Proxy.newProxyInstance() 在运行时生成代理类:

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 1. 创建真实对象
        Rent host = new Host();

        // 2. 创建调用处理器,并关联真实对象
        RentHandler handler = new RentHandler(host);

        // 3. 动态生成代理对象
        Rent proxy = (Rent) Proxy.newProxyInstance(
            Rent.class.getClassLoader(), // 使用接口的类加载器
            new Class[]{Rent.class},    // 代理类实现的接口
            handler                     // 方法调用的处理器
        );

        // 4. 通过代理对象调用方法
        proxy.rent();          // 会触发 RentHandler.invoke()
        int price = proxy.getPrice(); // 代理修改返回值
        System.out.println("最终价格:" + price);
    }
}

输出结果:

[代理] 正在检查租客信用...
房东出租房子
[代理] 租房完成,签订合同
[代理] 正在检查租客信用...
最终价格:4500

3. 动态代理的适用场景

  1. AOP(面向切面编程)

    日志记录、性能统计、事务管理。
  2. RPC(远程方法调用)

    动态代理隐藏网络通信细节(如 Dubbo、gRPC)。
  3. 权限控制

    在方法调用前检查用户权限。
  4. 缓存代理

    缓存方法返回值,避免重复计算。

4. 动态代理的优缺点

✅ 优点

  • 无侵入性:无需修改原有代码,直接增强功能。

  • 灵活扩展:一个 InvocationHandler 可代理多个接口。

  • 符合开闭原则:新增功能不影响原有逻辑。

❌ 缺点

  • 基于接口:只能代理接口,不能代理类(需用 CGLIB 弥补)。

  • 性能开销:反射调用比直接调用稍慢(但现代 JVM 已优化)。

动态代理 vs. 静态代理

特性动态代理静态代理
代理类生成时机运行时动态生成编译时手动编写
代码量少(通用性强)多(每个代理类需单独实现)
灵活性高(可代理任意接口)低(需为每个类编写代理)
性能稍慢(反射调用)快(直接调用)


五、代理模式的详细原理拆解

1. 接口的作用

  • 接口(如 FileAccess)确保代理和目标对象有相同的方法签名,客户端可以用统一的方式调用。
  • 这实现了开闭原则:可以替换不同的代理或目标对象,而不修改客户端代码。
  • 类似于你在网上购物时,无论是通过朋友(代理)还是直接去商店,购买流程(接口)是一致的。

2. 代理的控制逻辑

  • 代理通过持有的目标对象引用(realFileAccess)将请求委托给目标对象。
  • 代理可以在调用前后添加逻辑:
    • 前置逻辑:如权限检查、日志记录。
    • 后置逻辑:如清理资源、返回结果处理。
  • 在例子中,deleteFile 的权限检查是前置逻辑,日志记录是前置和后置逻辑的结合。

3. 客户端的透明性

  • 客户端通过接口(FileAccess)调用方法,无需知道背后是代理还是目标对象。
  • 这实现了封装:客户端只关心结果,不关心权限检查或日志记录的实现细节。

4. 延迟加载(虚拟代理的扩展)

虽然本例是保护代理,但可以扩展为虚拟代理。例如,realFileAccess 可以在第一次调用时才创建:

if (realFileAccess == null) {
    realFileAccess = new RealFileAccess(); // 延迟初始化
}

这就像你的朋友等到你确认要买衣服时才去商店,节省时间和资源。


六、代理模式的优点和缺点

优点

控制访问:代理可以限制对目标对象的访问(如权限检查)。

功能增强:可以在不修改目标对象的情况下添加日志、缓存等功能。

解耦:客户端与目标对象隔离,降低耦合度。

灵活性:可以动态切换代理逻辑(如根据用户角色选择不同代理)。

缺点

复杂性增加:引入代理对象使系统结构更复杂。

性能开销:代理的额外逻辑可能增加调用时间。

维护成本:需要维护代理和目标对象的同步(方法签名一致)。


七、实际应用场景

代理模式在现实开发中非常常见:

 1. Spring AOP:Spring 框架使用动态代理实现切面编程(如日志、事务管理)。

 2. 数据库连接池:代理控制数据库连接的分配和回收。

 3. Web 框架:代理处理 HTTP 请求的认证、路由等。

 4. 图片延迟加载:网页中图片只有在滚动到可视区域时才加载(虚拟代理)。


八、总结

        代理设计模式通过引入一个代理对象,控制对目标对象的访问,并在不修改目标对象的情况下添加额外功能。其核心是接口统一、委托调用、逻辑增强

通过保护代理的例子,我们看到:

 - 接口定义了代理和目标对象的契约。

 - 代理对象通过持有目标对象引用,拦截和增强客户端请求。

 - 客户端通过接口透明调用,无需关心代理的内部逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

15.第二阶段x64游戏实战-分析怪物血量(遍历周围)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 上一个内容:14.第二阶段x64游戏实战-分析人物的名字 如果想实现自动打怪,那肯定…

HarmonyOS 基础语法概述 UI范式

ArkUI框架 - UI范式 ArkTS的基本组成 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中Entry、Component和State都是装饰器,Component表示自定义组件,Entry表示该自定义组件为入口组件,Stat…

专题讨论2:树与查找

在讨论前先回顾一下定义: BST树的定义 二叉搜索树是一种特殊的二叉树,对于树中的任意一个节点: 若它存在左子树,那么左子树中所有节点的值都小于该节点的值。 若它存在右子树,那么右子树中所有节点的值都大于该节点…

django之数据的翻页和搜索功能

数据的翻页和搜素功能 目录 1.实现搜素功能 2.实现翻页功能 一、实现搜素功能 我们到bootstrap官网, 点击组件, 然后找到输入框组, 并点击作为额外元素的按钮。 我们需要使用上面红色框里面的组件, 就是搜素组件, 代码部分就是下面红色框框出来的部分。 把这里的代码复制…

unity脚本-FBX自动化模型面数校验

根据目前模型资源平均面数预算进行脚本制作,自动化校验模型面数是否符合规范。 *注:文件格式为.cs。需要放置在unity资源文件夹Assets>Editor下。 测试效果(拖一个fbx文件进unity时自动检测): 以下为完整代码 us…

C++用于保留浮点数的两位小数,使用宏定义方法(可兼容低版本Visual Studio)

文章目录 一、 描述二、 样例二、 结果输出 一、 描述 这个宏定义(可放入.h头文件里)使用基本的数学运算,几乎兼容所有版本的VS,以下可对正数做四舍五入: #define ROUND_TO_TWO(x) ( (floor((x) * 100 0.5) / 100) …

(51单片机)LCD显示温度(DS18B20教程)(LCD1602教程)(延时函数教程)(单总线教程)

演示视频: LCD显示温度 源代码 如上图将9个文放在Keli5 中即可,然后烧录在单片机中就行了 烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频: 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.…

服务器运维:服务器流量的二八法则是什么意思?

文章目录 用户行为角度时间分布角度应用场景角度 服务器流量的二八法则,又称 80/20 法则,源自意大利经济学家帕累托提出的帕累托法则,该法则指出在很多情况下,80% 的结果是由 20% 的因素所决定的。在服务器流量领域,二…

【LeetCode】嚼烂热题100【持续更新】

2、字母异位词分组 方法一&#xff1a;排序哈希表 思路&#xff1a;对每个字符串排序&#xff0c;排序后的字符串作为键插入到哈希表中&#xff0c;值为List<String>形式存储单词原型&#xff0c;键为排序后的字符串。 Map<String, List<String>> m new Ha…

赛灵思 XC7K325T-2FFG900I FPGA Xilinx Kintex‑7

XC7K325T-2FFG900I 是 Xilinx Kintex‑7 系列中一款工业级 (I) 高性能 FPGA&#xff0c;基于 28 nm HKMG HPL 工艺制程&#xff0c;核心电压标称 1.0 V&#xff0c;I/O 电压可在 0.97 V–1.03 V 之间灵活配置&#xff0c;并可在 –40 C 至 100 C 温度范围内稳定运行。该器件提供…

k8s-1.28.10 安装metrics-server

1.简介 Metrics Server是一个集群范围的资源使用情况的数据聚合器。作为一个应用部署在集群中。Metric server从每个节点上KubeletAPI收集指标&#xff0c;通过Kubernetes聚合器注册在Master APIServer中。为集群提供Node、Pods资源利用率指标。 2.下载yaml文件 wget https:/…

基于外部中中断机制,实现以下功能: 1.按键1,按下和释放后,点亮LED 2.按键2,按下和释放后,熄灭LED 3.按键3,按下和释放后,使得LED闪烁

题目&#xff1a; 参照外部中断的原理和代码示例,再结合之前已经实现的按键切换LED状态的实验&#xff0c;用外部中断改进其实现。 请自行参考文档《中断》当中&#xff0c;有关按键切换LED状态的内容, 自行连接电路图&#xff0c;基于外部中断机制&#xff0c;实现以下功能&am…

【我的创作纪念日】 --- 与CSDN走过的第365天

个人主页&#xff1a;夜晚中的人海 不积跬步&#xff0c;无以至千里&#xff1b;不积小流&#xff0c;无以成江海。-《荀子》 文章目录 &#x1f389;一、机缘&#x1f680;二、收获&#x1f3a1;三、 日常⭐四、成就&#x1f3e0;五、憧憬 &#x1f389;一、机缘 光阴似箭&am…

鸿蒙生态新利器:华为ArkUI-X混合开发框架深度解析

鸿蒙生态新利器&#xff1a;华为ArkUI-X混合开发框架深度解析 作者&#xff1a;王老汉 | 鸿蒙生态开发者 | 2025年4月 &#x1f4e2; 前言&#xff1a;开发者们的新机遇 各位鸿蒙开发者朋友们&#xff0c;是否还在为多平台开发重复造轮子而苦恼&#xff1f;今天给大家介绍一位…

‌信号调制与解调技术基础解析

调制解调技术是通信系统中实现基带信号与高频载波信号相互转换的主要技术&#xff0c;通过调整信号特性使其适应不同信道环境&#xff0c;保障信息传输的效率和可靠性。 调制与解调的基本概念 调制&#xff08;Modulation&#xff09;‌ 将低频基带信号&#xff08;如语音或数…

【扫描件批量改名】批量识别扫描件PDF指定区域内容,用识别的内容修改PDF文件名,基于C++和腾讯OCR的实现方案,超详细

批量识别扫描件PDF指定区域内容并重命名文件方案 应用场景 本方案适用于以下场景: 企业档案数字化管理:批量处理扫描的合同、发票等文件,按内容自动分类命名财务票据处理:自动识别票据上的关键信息(如发票号码、日期)用于归档医疗记录管理:从扫描的检查报告中提取患者I…

序列决策问题(Sequential Decision-Making Problem)

序列决策问题&#xff08;Sequential Decision-Making Problem&#xff09;是强化学习&#xff08;Reinforcement Learning, RL&#xff09;的核心研究内容&#xff0c;其核心思想是&#xff1a;​​智能体&#xff08;Agent&#xff09;需要在连续的时间步骤中&#xff0c;通过…

L2-1、打造稳定可控的 AI 输出 —— Prompt 模板与格式控制

一、为什么需要 Prompt 模板&#xff1f; 在与 AI 模型交互时&#xff0c;我们经常会遇到输出不稳定、格式混乱的问题。Prompt 模板帮助我们解决这些问题&#xff0c;通过结构化的输入指令来获得可预测且一致的输出结果。 模板的作用主要体现在&#xff1a; 固定输出格式&am…

LLM中什么是模板定义、提示工程和文档处理链

LLM中什么是模板定义、提示工程和文档处理链 定义提示模板(prompt_template):prompt_template = """Use the following pieces of context to answer the question at the end. If you dont know the answer, just say that you dont know, dont try to make…

密码学(二)流密码

2.1流密码的基本概念 流密码的基本思想是利用密钥 k 产生一个密钥流...&#xff0c;并使用如下规则对明文串 ... 加密&#xff1a;。密钥流由密钥流发生器产生&#xff1a; &#xff0c;这里是加密器中的记忆元件&#xff08;存储器&#xff09;在时刻 i 的状态&#xff0c…