当前位置: 首页 > news >正文

设计模式之代理模式

本文中涉及到的完整代码存放于以下 GitHub 仓库中 LearningCode

1. 理论部分

代理模式(Proxy Pattern):给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

代理模式也称委托模式。

1.1 结构与实现

代理模式包含以下 3 个角色:

  • Subject(抽象主题角色)
    • 职责:定义 RealSubject 和 Subject 的共用接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy。
    • 实现:通常声明为接口或抽象类,也可以是具体类。
  • Proxy(代理主题角色)
    • 职责:包含了对 RealSubject 的引用,从而可以在任何时候操作 RealSubject;实现 Subject 中定义的接口,以便在任何时候替代 RealSubject;可以控制对 RealSubject 的使用,负责在需要的时候创建和删除 RealSubject,并对 RealSubject 的使用加以约束。
    • 实现:通常声明为具体类。
  • RealSubject(真实主题角色)
    • 职责:定义 Proxy 所代表的真实对象,实现了 Subject 中定义的接口,编写了真实的业务逻辑,客户端可以通过 Proxy 间接调用 RealSubject 中定义的接口。
    • 实现:通常声明为具体类。

代理模式的 UML 类图如下所示:
在这里插入图片描述

1.2 扩展:动态代理

传统代理模式的缺陷:在传统的代理模式中,Subject 和 RealSubject 都应该是事先已经存在的,代理类的接口和所代理方法都已明确指定,也称为静态代理。如果需要为不同的 RealSubject 提供代理类或者代理一个 RealSubject 中的不同方法,都需要增加新的代理类,这将导致系统中的类个数急剧增加。

**动态代理(Dynamic Proxy)**可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。动态代理的实现依赖于语言特性,例如在 Java 中实现依赖于 Proxy 类。

1.3 优缺点与适用场景

代理模式具有以下优点:

  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合。
  • 客户端可以针对 Subject 进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

此外,不同类型的代理模式具有独特优点:

  • 远程代理为位于两个不同地址空间的对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高了系统的整体运行效率。
  • 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  • 缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
  • 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。

代理模式存在以下缺点:

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

当需要在访问一个对象时进行一些控制或额外处理时,使用代理模式。下面介绍一些可以适用代理模式的常见情况:

  • 远程代理(Remote Proxy):为一个位于不同地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中。
  • 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 智能引用代理(Smart Reference Proxy):当一个对象被引用时提供一些额外的操作,例如将对象被调用的次数记录下来等。

2. 实现部分

以 Java 代码为例,演示代理模式的实现。
案例介绍:
在这里插入图片描述

2.1 静态代理

定义抽象主题角色——Searcher

public interface Searcher {String doSearch(String userId, String keyword);
}

定义真实主题角色——RealSearcher

public class RealSearcher implements Searcher{@Overridepublic String doSearch(String userId, String keyword) {System.out.println("用户'" + userId + "'使用关键词'" + keyword + "'查询商务信息!");return "返回具体内容";}
}

定义代理主题角色——ProxySearcher

public class ProxySearcher implements Searcher{private final RealSearcher searcher = new RealSearcher();private AccessValidator validator;private Logger logger;@Overridepublic String doSearch(String userId, String keyword) {if (validate(userId)) {String result = searcher.doSearch(userId, keyword);log(userId);return result;}else {return null;}}private boolean validate(String userId) {validator = new AccessValidator();return validator.validate(userId);}private void log(String userId) {logger = new Logger();logger.log(userId);}
}

客户端调用:

public class Main {public static void main(String[] args) {// 可以使用环境变量优化 Searcher 实例的获取Searcher searcher = new ProxySearcher();System.out.println(searcher.doSearch("杨过", "玉女心经"));}
}

完整的 UML 类图如下所示:
在这里插入图片描述

2.2 动态代理:JDK实现

创建代理工厂

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;public class ProxyFactory {@SuppressWarnings("unchecked")public static  <T> T getProxy(T t)  {return (T) Proxy.newProxyInstance(t.getClass().getClassLoader(),new Class[]{Searcher.class},new InvocationHandler() {private AccessValidator validator;private Logger logger;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{if (method.getName().equals("doSearch") &&Arrays.equals(method.getParameterTypes(), new Class[]{String.class, String.class})) {String userId = (String) args[0];if (validate(userId)) {Object obj = method.invoke(t, args);log(userId);return obj;}else {return null;}}else {return method.invoke(t,args);}}private boolean validate(String userId) {validator = new AccessValidator();return validator.validate(userId);}private void log(String userId) {logger = new Logger();logger.log(userId);}});}}

客户端调用

public class Main {public static void main(String[] args) {Searcher searcher = ProxyFactory.getProxy(new RealSearcher());System.out.println(searcher.doSearch("杨过", "玉女心经"));}
}

2.3 动态代理:CGLIB 实现

基于 JDK 的动态代理要求被代理类至少有一个父接口,对于没有实现接口的类无能为力,而社区的 CGLIB 库支持对任何类进行代理。当然,final方法或者final 类也无能为力。
引入依赖

	<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>

修改代理工厂:

import java.lang.reflect.Method;
import java.util.Arrays;public class ProxyFactory {@SuppressWarnings("unchecked")public static  <T> T getProxy(T t)  {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Searcher.class);enhancer.setCallback(new MethodInterceptor() {private AccessValidator validator;private Logger logger;@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("doSearch") &&Arrays.equals(method.getParameterTypes(), new Class[]{String.class, String.class})) {String userId = (String) args[0];if (validate(userId)) {Object obj = method.invoke(t, args);log(userId);return obj;}else {return null;}}else {return method.invoke(t,args);}}private boolean validate(String userId) {validator = new AccessValidator();return validator.validate(userId);}private void log(String userId) {logger = new Logger();logger.log(userId);}});return (T) enhancer.create();}
}

3. 参考资料

学习视频:

  1. 设计模式快速入门 —— 图灵星球TuringPlanet —— 代理模式:https://www.bilibili.com/video/BV1xg4y1T7vh
  2. Java设计模式详解 —— 黑马程序员 —— 代理模式(P56 ~ P62):https://www.bilibili.com/video/BV1Np4y1z7BU?p=56
  3. Java设计模式 —— 尚硅谷 —— 代理模式(P91 ~ P95):https://www.bilibili.com/video/BV1G4411c7N4?p=91

学习读物:

  1. 《设计模式:可复用面向对象软件的基础》—— Erich Gamma 著 —— 李英军 译 —— 第 4.7 节(P155)
  2. 《Java 设计模式》 —— 刘伟 著 —— 第 15 章(P203)
  3. 《设计模式之美》—— 王争 著 —— 第 7.1 节(P209)
  4. 《设计模式之禅》 —— 第 2 版 —— 秦小波 著 —— 第 12 章(P113)
  5. 《图解设计模式》—— 结城浩 著 —— 杨文轩 译 —— 第 21 章(P249)

电子文献:

  1. 设计模式教程 —— 菜鸟教程 —— 代理模式:https://www.runoob.com/design-pattern/proxy-pattern.html
  2. 99+ 种软件模式 —— long2ge —— 代理模式:https://learnku.com/docs/99-software-pattern/proxy-pattern/11963
http://www.xdnf.cn/news/1218961.html

相关文章:

  • 网关 + MDC 过滤器方案,5分钟集成 日志 traceid
  • React中的this绑定
  • node.js之Koa框架
  • Linux Flathub软件管理方法 使用指南
  • [12月考试] E
  • 进程控制:从创建到终结的完整指南
  • 【Django】-1- 开发项目搭建
  • MongoDB系列教程-第四章:MongoDB Compass可视化和管理MongoDB数据库
  • 抓大鹅小游戏微信抖音流量主小程序开源
  • HUD抬头显示器-杂散光测试设备 太阳光模拟器
  • AI学习笔记三十三:基于Opencv的单目标跟踪
  • 对git 熟悉时,常用操作
  • day36 力扣1049.最后一块石头的重量II 力扣494.目标和 力扣474.一和零
  • 【LeetCode 热题 100】4. 寻找两个正序数组的中位数——(解法一)线性扫描
  • [论文阅读] 人工智能 + 软件工程 | KnowledgeMind:基于MCTS的微服务故障定位新方案——告别LLM幻觉,提升根因分析准确率
  • SFT最佳实践教程 —— 基于方舟直接进行模型精调
  • 构型空间(Configuration Space,简称C-space)
  • 全基因组关联分析(GWAS)中模型参数选择:MLM、GLM与FarmCPU的深度解析
  • 数据库中使用SQL作分组处理01(简单分组)
  • 【worklist】worklist的hl7、dicom是什么关系
  • 学以致用——用Docker搭建ThinkPHP开发环境
  • 深入探索Weaviate:构建高效AI应用的数据库解决方案
  • 《人工智能导论》(python版)第2章 python基础2.2编程基础
  • 大模型流式长链接场景下 k8s 优雅退出 JAVA
  • PHP 与 MySQL 详解实战入门(1)
  • 零基础构建MCP服务器:TypeScript/Python双语言实战指南
  • 在幸狐RV1106板子上用gcc14.2本地编译安装samba-4.22.3服务器,并且支持XP系统访问共享文件夹
  • 基于单片机胎压检测/锅炉蒸汽压力/气压检测系统
  • LCM中间件入门(2):LCM核心实现原理解析
  • InfluxDB 与 Python 框架结合:Django 应用案例(二)