代理模式笔记
代理模式
- 代理模式
- 代理模式的应用场景
- 先理解什么是代理,再理解动静态
- 举例
- 举例所用代码
- 动静态的区别
- 静态代理
- 动态代理
- 动态代理的优点
- 代理模式与装饰者模式的区别
代理模式
代理模式在设计模式中是7种结构型模式中的一种,而代理模式有分动态代理,静态代理,一般来说,动态代理更加常用一些。
代理模式的应用场景
这些应用场景除了日志记录,我也没有熟悉的,刚接触代理模式的可以直接跳过
远程代理(Remote Proxy):
当对象存在于不同的地址空间,例如在网络中的不同服务器上时,可以使用代理模式实现远程代理。代理对象充当本地对象的代表,隐藏了远程对象的实际细节,使得客户端可以像调用本地对象一样调用远程对象。
虚拟代理(Virtual Proxy):
虚拟代理用于按需创建昂贵或复杂的对象,以提高系统性能。代理对象在真正需要执行操作时才会实例化真实对象,而在其他情况下,它充当一个占位符。
保护代理(Protection Proxy):
保护代理用于控制对对象的访问权限。代理对象可以根据访问者的身份控制其对真实对象的访问,例如,检查用户是否具有足够的权限来执行某个操作。
缓存代理(Cache Proxy):
缓存代理用于缓存一些开销较大的操作的结果,以避免重复计算。代理对象在执行真实对象的操作之前检查是否已经有相应的结果缓存,如果有则直接返回缓存的结果。
日志记录代理(Logging Proxy):
日志记录代理用于在调用真实对象的操作前后记录相关日志信息,例如,记录方法的执行时间、参数、返回值等,以便进行调试或性能分析。
智能引用代理(Smart Reference Proxy):
智能引用代理用于在对象被引用时执行一些额外的操作,例如,对对象的引用计数进行管理,当引用计数为零时释放对象资源。
延迟加载代理(Lazy Loading Proxy):
延迟加载代理用于延迟加载对象的实例,即在真正需要使用对象时才进行加载。这可以提高系统的启动性能,避免在启动时加载不必要的资源。
先理解什么是代理,再理解动静态
代理模式的灵魂就是在不直接访问某个对象的情况下,通过代理对象来间接访问并控制对该对象的访问。(在这个间接访问的过程中代理对象通常会在执行代理对象里的操作先后时间段里执行一些被代理对象里没有的操作)
我们先搞清楚代理模式有几个角色,再来举例
真实对象(被代理对象): 被间接访问的对象
代理对象: 代理对象将间接访问真实对象,并且代理可以帮助你做一些额外的事情,比如检查你的权限、记录你的请求、或者缓存结果。
抽象类或接口(一般是接口): 这是代理对象和真实对象都要实现的接口(建立一个联系),这样代理才可以替代真实对象。一般这个接口里的抽象方法是代理对象访问被代理对象的关键。
举例
一天,四年级三班同学举行班级里的数学期中考试,考试时间结束后,由小明同学(数学学习委员)将试卷收好送给数学老师,数学老师批改完试卷后,小明又会将试卷拿回,并将班级数学成绩统计出来。
在了解完代理模式的三个角色后,我们尝试把上面的例子进行角色分析
真实对象(被代理对象):数学老师
你可以理解成数学老师才是期中数学考试出成绩的关键,但在同学们知晓成绩时,他并没有出面。
代理对象:小明——数学学习委员
虽然数学老师才是数学考试出成绩的关键,但是出成绩时,他才是在同学们露面的人,并且他还额外进行了统计成绩的操作(类似程序的日志记录)。
抽象类或者接口:期中考试
期中考试是联系数学老师和数学学习委员的一个关键,当然你也可以用其他的关键词来描述这个接口,但是批改试卷是这个例子的关键,如果不用学生可以自己批改试卷,那么老师就不用出现了,就不用访问数学老师这个对象了,所以接口里必须要有批改试卷这个方法。
举例所用代码
这里我们先创建抽象接口
public interface IMidterm_Examination {//批改试卷的抽象方法void markPapers();
}
再创建具体的数学老师类,也就是真实对象类或者说被代理类
// 在实现抽象接口的前提下创建真实对象
public class MathTeacher implements IMidterm_Examination {//基本属性private int age=32;private String name="李四";private String job="数学老师";@Overridepublic void markPapers() {System.out.println("数学老师正在改试卷");}
}
在创建代理对象,创建代理对象前需先写出代理对象类
public class MathMonitor implements IMidterm_Examination{private int age=16;private String name="小明";private String job="数学学习委员";private IMidterm_Examination target;//代理目标,即被代理对象,接口是为了更加灵活通用public MathMonitor(IMidterm_Examination target) {this.target = target;//在构造代理对象时,将被代理对象传入}void sendPaper(){System.out.println("小明同学将试卷送给老师");}//小明额外的统计成绩方法void countScores(){System.out.println("小明同学正在统计成绩");}@Overridepublic void markPapers() {//重写抽象接口里的方法//显示小明同学将试卷送给数学老师this.sendPaper();//老师来修改试卷target.markPapers();//老师批改完试卷后,小明统计成绩this.countScores();System.out.println("期中考试流程结束");}
}
测试主函数
public class Main {public static void main(String[] args) {//通过接口方式创建被代理对象,IMidterm_Examination mathTeacher=new MathTeacher();//再通过接口创建代理对象IMidterm_Examination mathMonitor=new MathMonitor(mathTeacher);//通过代理对象间接访问数学老师这个对象mathMonitor.markPapers();}
}
运行结果
在看完上面的例子后,我们知道代理模式中的代理就是一个类似中介的效果,就像找工作一样,小王本来想进某家电子厂的,但是需要交中介费才能进入这个厂,但是这个中介还会包你不满意该电子厂环境拒绝进厂来回的路费一样。
动静态的区别
静态代理
静态代理在上面的举例代码中已经体现出了,它有以下特点:
(也许你在读完这些特点你还是会不太理解,所以你可以在看完动态代理之后再来回顾静态代理,才能感受到这些特点。)
编译时确定:
在编译期间,代理类的代码就已经确定。这意味着代理类的结构在编译时就已经固定,不会在运行时改变。
代理类固定:
静态代理需要为每个被代理的类创建一个代理类。这意味着如果要代理多个类,就需要为每个类编写一个对应的代理类。
低灵活性:
由于代理类在编译时已经确定,因此静态代理的灵活性相对较低。如果需要修改代理类的行为,通常需要修改代理类的源代码,并重新编译。
性能较高:
静态代理的方法调用在编译期间就已经确定,因此在运行时的性能通常比动态代理高。代理对象直接调用被代理对象的方法,不需要进行额外的方法查找或调用。
动态代理
我们将之前那个例子稍微拓展一下,四年级三班的同学上午进行了数学期中考试后,下午又进行了英语期中考试,可是四年级三班的英语课代表生病请假了,所以英语老师也麻烦小明同学(数学学习委员)来收试卷并且把试卷送给老师,最后再把试卷送到班级里。
你先别急着否认这个静态代理做不到,静态代理同样能完成这件事情,我们再试着用静态代理来完成这件事,
我们回顾一下之前小明同学的代码:
我们的代理对象的构造函数中的参数是接口,那么理论上只要英语老师也实现这个接口,他也能传进去,那就试试。
public class MathMonitor implements IMidterm_Examination{private int age=16;private String name="小明";private String job="数学学习委员";private IMidterm_Examination target;//代理目标,即被代理对象,接口是为了更加灵活通用public MathMonitor(IMidterm_Examination target) {this.target = target;//在构造代理对象时,将被代理对象传入}
我们定义的接口不变
public interface IMidterm_Examination {//批改试卷的抽象方法void markPapers();
}
再来创建一个英语老师的被代理对象的类实现期中考试接口:
public class EnglishTeacher implements IMidterm_Examination{private int age=24;private String name="王雪";private String job="英语老师";@Overridepublic void markPapers() {System.out.println("英语老师正在批改英语试卷");}
}
只需要在测试主函数中传入英语老师这个被代理对象,就行了
public static void main(String[] args) {//通过接口创建被代理对象,IMidterm_Examination mathTeacher=new MathTeacher();//再通过接口创建代理对象IMidterm_Examination mathMonitor=new MathMonitor(mathTeacher);//通过代理对象间接访问数学老师这个对象mathMonitor.markPapers();//通过创建被代理对象,IMidterm_Examination englishTeacher=new EnglishTeacher();mathMonitor=new MathMonitor(englishTeacher);//通过代理对象间接访问英语老师这个对象mathMonitor.markPapers();}
运行结果:
其实这么来说,静态代理也有点“动”的意思,一个代理对象也能完成多个被代理对象的代理。
但是在真正的动态代理面前,它还差远了。
前提是被代理类与代理类实现了相同的接口
动态代理的最大特色是代理类不需要实现与被代理类相同的接口就能实现代理,也就是说代理类在java中,动态代理一般有JDK接口和CGLib两种方式进行实现,这里只介绍JDK接口方法。
我们动态代理来完成上面的例子
需要大改代码的就是代理类,也就是小明这个数学学习委员的代码,
代理类不在需要我们自定义的期中考试的接口
但它需要实现官方提供的InvocationHandler接口
public class MathMonitor implements InvocationHandler {private int age=16;private String name="小明";private String job="数学学习委员";private Object target;public MathMonitor(Object target) {this.target = target;}void sendPaper(){System.out.println("小明同学将试卷送给老师");}//小明额外的统计成绩方法void countScores(){System.out.println("小明同学正在统计成绩");}/** @param* Object proxy 传入代理对象* Method method 传入需要执行的方法* Object[] args 方法需要的参数数组* @return 返回一个Object类型的对象**/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//在间接访问对象前可做的事情this.sendPaper();Object result = method.invoke(target, args);//在间接访问对象后可做的事情this.countScores();return result;//在Java的反射API中,Method 类的 invoke 方法用于动态地调用一个方法。在你提供的 invoke 方法中,//这个 method.invoke(target, args) 调用实际上是在执行被代理对象(target)上的方法,// 并且传递了参数(args)。//method.invoke 的返回值就是该方法调用的结果。换句话说,它返回了被代理对象上被调用的方法的返回值。//如果方法类型是void,那么result值是null值}
}
测试主函数里的代码也有点变化
//创建两个被代理对象IMidterm_Examination mathTeacher =new MathTeacher();IMidterm_Examination englishTeacher=new EnglishTeacher();//先代理数学老师MathMonitor mathMonitor=new MathMonitor(mathTeacher);IMidterm_Examination proxy = (IMidterm_Examination) Proxy.newProxyInstance(Main.class.getClassLoader(),//获取类加载器new Class[]{IMidterm_Examination.class},mathMonitor);proxy.markPapers();mathMonitor=new MathMonitor(englishTeacher);proxy = (IMidterm_Examination) Proxy.newProxyInstance(Main.class.getClassLoader(),//获取类加载器new Class[]{IMidterm_Examination.class},mathMonitor);proxy.markPapers();}
}
Proxy.newProxyInstance 是 Java 动态代理的核心方法,用于创建一个新的代理实例。这个方法需要三个参数:
类加载器(ClassLoader):Main.class.getClassLoader()
类加载器用于加载代理类。在 Java 中,每个类都有一个类加载器,它负责加载类的字节码文件。在动态代理中,代理类是在运行时生成的,因此需要一个类加载器来加载这个新生成的类。在这个例子中,使用 Main.class.getClassLoader() 获取 Main 类的类加载器来加载代理类。
代理接口数组(Class<?>[] interfaces):new Class[]{IMidterm_Examination.class}
这个参数指定了代理实例需要实现的接口列表。代理实例将实现这些接口中定义的所有方法。当代理实例上的这些方法被调用时,它们将被转发到 InvocationHandler 的 invoke 方法。在这个例子中,代理实例将实现 IMidterm_Examination 接口。
调用处理器(InvocationHandler):mathMonitor
InvocationHandler 是一个接口,它里面定义了一个 invoke 方法,用于处理代理实例上的方法调用。即传入代理对象
动态代理的优点
动态代理是一种在运行时动态创建代理对象的机制,它允许你在调用实际对象之前或之后执行额外的操作。以下是动态代理的一些优点:
灵活性:
动态代理允许你在运行时创建代理对象,因此你可以根据需要动态地选择要代理的对象,而无需在编译时确定。这使得代码更加灵活和可扩展。
减少重复代码:
通过使用动态代理,你可以将一些通用的代码逻辑(例如日志记录、性能监控、事务管理等)从业务逻辑中分离出来,并将其放入代理对象中。这样可以减少重复代码,提高代码的可维护性。
简化代码结构:
动态代理可以帮助你将关注点分离(Separation of Concerns),将横切关注点(cross-cutting concerns)从核心业务逻辑中解耦。这样可以使得代码结构更加清晰,易于理解和维护。
提高代码复用性:
通过将通用的功能封装在代理对象中,可以使得这些功能在多个地方被重复使用,从而提高了代码的复用性。
动态性:
由于动态代理是在运行时创建的,因此你可以根据需要动态地添加、修改或删除代理对象的行为,而无需修改原始对象或重新编译代码。这使得系统更加灵活和动态。
代理模式与装饰者模式的区别
在学习中,我很容易把装饰器模式和代理模式混淆,老师说在现实开发中,确实是两个都会混着用的,但是它们还是有一点小区别的。
意图不同:
代理模式的主要目的是控制对对象的访问。代理对象通常作为原始对象的接口,允许你在不直接访问原始对象的情况下控制对其的访问。
装饰者模式的主要目的是为对象动态添加新的功能。装饰者模式允许你通过将对象包装在一个或多个装饰者中,来动态地添加或修改对象的行为,而不需要改变其接口。
关注点不同:
代理模式的关注点在于控制对对象的访问,通常涉及在访问原始对象之前或之后执行额外的操作,如权限验证、延迟加载、缓存等。
装饰者模式的关注点在于动态地为对象添加新的行为,通常涉及在对象的行为上面添加修饰,如增加新的功能、改变行为等。
组合方式不同:
代理模式通常是一对一的关系,即每个代理对象只代理一个真实对象,并通过这个代理对象来控制对真实对象的访问。
装饰者模式则可以是多对一的关系,即一个对象可以被多个装饰者对象装饰,每个装饰者对象可以在不影响其他装饰者的情况下独立地添加新的行为。
生命周期不同:
代理模式的生命周期通常与被代理对象相关联,代理对象的创建和销毁由被代理对象的创建和销毁来管理。
装饰者模式的生命周期则通常是短暂的,装饰者对象通常是在运行时动态添加到被装饰对象上,可以根据需要随时添加或删除。
相关文章:

代理模式笔记
代理模式 代理模式代理模式的应用场景先理解什么是代理,再理解动静态举例举例所用代码 动静态的区别静态代理动态代理 动态代理的优点代理模式与装饰者模式的区别 代理模式 代理模式在设计模式中是7种结构型模式中的一种,而代理模式有分动态代理&#x…...

手机中有哪些逆向进化的功能
手机中有哪些逆向进化的功能?逆向进化是指明明很优秀的很方便的功能,却因为成本或者其他工业原因莫名其妙地给取消了。 逆向进化1:可拆卸电池-变为不可拆卸电池。 智能手机为了追求轻薄等原因,所以移除了可拆卸电池功能。将电池…...

LeetCode24.两两交换链表中的节点
参考链接:代码随想录:LeetCode24.两两交换链表中的节点 我这里使用了3个变量进行暴力交换,简单快捷!但是有一点想不明白,return这里只能写dh->next,写返回head就结果不对了!但是后面又想明白了ÿ…...

Eureka注册中心(黑马学习笔记)
Eureka注册中心 假如我们的服务提供者user-service部署了多个实例,如图: 大家思考几个问题: order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口? 有多个user-service实例地址,…...

unity-firebase-Analytics分析库对接后数据不显示原因,及最终解决方法
自己记录一下unity对接了 FirebaseAnalytics.unitypackage(基于 firebase_unity_sdk_10.3.0 版本) 库后,数据不显示的原因及最终显示解决方法: 1. 代码问题(有可能是代码写的问题,正确的代码如下ÿ…...

JWT(JSON Web Token)原理、应用与安全性分析
随着互联网的快速发展,Web应用的安全性越来越受到重视。在众多的安全认证技术中,JSON Web Token(JWT)凭借其简洁、自包含和传输安全的特点,被广泛应用于Web应用的用户身份验证和信息交换。 一、JWT的原理 JWT是一个开…...

Redis 缓存(Cache)
什么是缓存 缓存(cache)是计算机中的一个经典的概念在很多场景中都会涉及到。 核心思路就是把一些常用的数据放到触手可及(访问速度更快)的地方,方便随时读取。 这里所说的“触手可及”是个相对的概念 我们知道,对于硬件的访问速度来说,通常…...

ChatGPT回答模式
你发现了吗,ChatGPT的回答总是遵循这些类型方式。 目录 1.解释模式 2.类比模式 3.列举模式 4.限制模式 5.转换模式 6.增改模式 7.对比模式 8.翻译模式 9.模拟模式 10.推理模式 1.解释模式 ChatGPT 在回答问题或提供信息时,不仅仅给出…...

戏曲文化苑|戏曲文化苑小程序|基于微信小程序的戏曲文化苑系统设计与实现(源码+数据库+文档)
戏曲文化苑小程序目录 目录 基于微信小程序的戏曲文化苑系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、微信小程序前台 2、管理员后台 (1)戏曲管理 (2)公告信息管理 (3)公告类型管理…...

Mysql数据库主从集群从库Slave因为RelayLog过多过大引起服务器硬盘爆满生产事故实战解决
Mysql数据库主从集群从库slave因为RelayLog过多过大引起从库服务器硬盘爆满生产事故实战解决 一、MySQL数据库主从集群概念 MySQL数据库主从集群是一种高可用性和读写分离的数据库架构,它基于MySQL的复制(Replication)技术来同步数据。在主…...

QT基本组件
四、基本组件 Designer 设计师(重点) Qt包含了一个Designer程序,用于通过可视化界面设计开发界面,保存文件格式为.ui(界面文件)。界面文件内部使用xml语法的标签式语言。 在Qt Creator中创建文件时…...

uniapp实现全局悬浮框
uniapp实现全局悬浮框(按钮,页面,图片自行设置) 可拖动 话不多说直接上干货 1,在components新建组件(省去了每个页面都要引用组件的麻烦) 2,实现代码 <template><view class"call-plate" :style"top: top px;left: left px;" touchmove&quo…...

C语言特殊函数
静态函数 背景知识:普通函数都是跨文件可见的,即在文件 a.c 中定义的函数可以在 b.c 中使用。 静态函数:只能在定义的文件内可见的函数,称为静态函数。 语法 staitc void f(void) // 在函数头前面增加关键字 static ÿ…...

全栈开发(TS,React,Vue, Java, 移动端flutter)接单
个人主页 https://hz.minicv.net/ 技术栈 前端:NextJS React VueJS 后端:NestJS Java 移动端:Flutter 其他:SpringCloud Redis Kafka Zookeeper 项目案例 微行简历( TS 全栈项目,一个极简的简历管理平…...

vue3使用百度地图
前情提要: 本文vue采用vue3框架,使用百度地图通过组件vue-baidu-map-3x: 组件官网:地图容器 | vue-baidu-map-3x 使用百度地图需要 申请百度地图AK秘钥 步骤:1.进入百度地图开放平台 | 百度地图API SDK | 地图开…...

docker 安装达梦dm8 包含lincese
1.加载达梦数据库docker镜像 dm_v8.1.1.66_x86_rh7_64_ent.tar为申请的镜像文件。 docker load -i dm_v8.1.1.66_x86_rh7_64_ent.tar 查看镜像 docker images 创建达梦数据库容器 执行创建命令: docker run -d -p 30236:5236 --restartalways --name dm8_test…...

golang入门介绍-1
今天开始发布关于go语言入门到实战内容,各位小伙伴准备好。 go介绍 Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。是由 Google 公司开发的一种静态强类型、编译型、并发型、并具有垃圾回收功能的编程语言。 Go 是…...

273.【华为OD机试真题】园区参观路径(动态规划-JavaPythonC++JS实现)
🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-园区参观路径二.解题思路三.题解代码Python题解…...

ChatGPT调教指南 | 咒语指南 | Prompts提示词教程(二)
在我们开始探索人工智能的世界时,了解如何与之有效沉浸交流是至关重要的。想象一下,你手中有一把钥匙,可以解锁与OpenAI的GPT模型沟通的无限可能。这把钥匙就是——正确的提示词(prompts)。无论你是AI领域的新手&#…...

超市售货|超市售货管理小程序|基于微信小程序的超市售货管理系统设计与实现(源码+数据库+文档)
超市售货管理小程序目录 目录 基于微信小程序的超市售货管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、微信小程序前台 2、管理员后台 (1)商品管理 (2)出入库管理 (3)公告管理 …...

流程图:理解、创建与优化的视觉工具
流程图:理解、创建与优化的视觉工具 引言 在日常生活和工作中,我们经常遇到需要描述一系列步骤或过程的情况。这些步骤可能是制作一杯咖啡、完成一个项目,或者是解决一个复杂的数学问题。流程图,作为一种强大的视觉工具…...

Java EE面试题解析(下)
21、 什么是Servlet?【掌握】 Servlet是使用Java Servlet应用程序接口(API)及相关类和方法的Java程序。所有的Servlet都必须要实现的核心接口是javax.servlet.servlet。每一个Servlet都必须要直接或者间接实现这个接口,或者继承j…...
从零开始手写mmo游戏从框架到爆炸(二十一)— 战斗系统二
导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客 上一章(从零开始手写mmo游戏从框架到爆炸(二十)— 战斗系统一-CSDN博客)我们只是完成了基本的战斗,速度属性并没有…...

强化学习入门(Matlab2021b)-创建环境【3】
目录 1 前言2 根据类模板创建自定义环境2.1 创建类模板2.2 Environment properties(环境特性)2.3 Required Functions(需要的环境方法)2.3.1 Constructor function(构造函数)2.3.2 reset function2.3.3 step function2.4 Optional Functions(可选的环境方法)2.5 Environment Vi…...

C++如何避免float误差?
C如何避免float误差? 在开始前我有一些资料,是我根据网友给的问题精心整理了一份「c的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!! …...

深信服技术认证“SCCA-C”划重点:深信服超融合HCI
为帮助大家更加系统化地学习云计算知识,高效通过云计算工程师认证,深信服特推出“SCCA-C认证备考秘笈”,共十期内容。“考试重点”内容框架,帮助大家快速get重点知识 划重点来啦 *点击图片放大展示 深信服云计算认证(S…...

Vue3路由元信息
路由元信息即定义路由时的meta信息 使用路由元信息定义页面在浏览器显示的标题 定义路由 const router createRouter({history:createWebHistory(import.meta.env.BASE_URL),routes:[{path:"/",component:()>import("/components/Login.vue"),meta:{…...

实用区块链应用:去中心化投票系统的部署与实施
一、需求分析背景 随着技术的发展,传统的投票系统面临着越来越多的挑战,如中心化控制、透明度不足和易受攻击等问题。为了解决这些问题,我们可以利用区块链技术去中心化、透明性和安全性来构建一个去中心化投票系统。这样的系统能够确保投票过…...

Flink中的双流Join
1. Flink中双流Join介绍 Flink版本Join支持类型Join API1.4innerTable/SQL1.5inner,left,right,fullTable/SQL1.6inner,left,right,fullTable/SQL/DataStream Join大体分为两种:Window Join 和 Interval Join 两种。 Window Join又可以根据Window的类型细分为3种…...

协程源码 launch 流程跟踪学习
为了更深入学习协程的底层实现原理,了解协程线程切换的根本本质。也为了以后在工作中可以根据不同的需求场景,更加随心所欲的使用不同的协程。 今天通过 launch 跟踪一下协程的执行流程。 fun getData() {Trace.beginSection("getData");Log.…...