代理模式笔记

代理模式

  • 代理模式
  • 代理模式的应用场景
  • 先理解什么是代理,再理解动静态
    • 举例
    • 举例所用代码
  • 动静态的区别
    • 静态代理
    • 动态代理
  • 动态代理的优点
  • 代理模式与装饰者模式的区别

代理模式

代理模式在设计模式中是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)从核心业务逻辑中解耦。这样可以使得代码结构更加清晰,易于理解和维护。

提高代码复用性:

通过将通用的功能封装在代理对象中,可以使得这些功能在多个地方被重复使用,从而提高了代码的复用性。

动态性:

由于动态代理是在运行时创建的,因此你可以根据需要动态地添加、修改或删除代理对象的行为,而无需修改原始对象或重新编译代码。这使得系统更加灵活和动态。

代理模式与装饰者模式的区别

在学习中,我很容易把装饰器模式和代理模式混淆,老师说在现实开发中,确实是两个都会混着用的,但是它们还是有一点小区别的。
意图不同:

代理模式的主要目的是控制对对象的访问。代理对象通常作为原始对象的接口,允许你在不直接访问原始对象的情况下控制对其的访问。
装饰者模式的主要目的是为对象动态添加新的功能。装饰者模式允许你通过将对象包装在一个或多个装饰者中,来动态地添加或修改对象的行为,而不需要改变其接口。

关注点不同:

代理模式的关注点在于控制对对象的访问,通常涉及在访问原始对象之前或之后执行额外的操作,如权限验证、延迟加载、缓存等。
装饰者模式的关注点在于动态地为对象添加新的行为,通常涉及在对象的行为上面添加修饰,如增加新的功能、改变行为等。

组合方式不同:

代理模式通常是一对一的关系,即每个代理对象只代理一个真实对象,并通过这个代理对象来控制对真实对象的访问。
装饰者模式则可以是多对一的关系,即一个对象可以被多个装饰者对象装饰,每个装饰者对象可以在不影响其他装饰者的情况下独立地添加新的行为。

生命周期不同:

代理模式的生命周期通常与被代理对象相关联,代理对象的创建和销毁由被代理对象的创建和销毁来管理。
装饰者模式的生命周期则通常是短暂的,装饰者对象通常是在运行时动态添加到被装饰对象上,可以根据需要随时添加或删除。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx 配置前端工程项目二级目录

前提&#xff1a; 前端工程技术框架: vue 后端工程技术工程&#xff1a;spring boot 需求&#xff1a;需要通过二级目录访问前端工程&#xff1a; 如之前&#xff1a;http://127.0.0.1:80/ 改成 http://127.0.0.1/secondDirectory:80/ 一.前端工程支持二级目录 1.编译文…

(十八)devops持续集成开发——使用docker安装部署jenkins流水线服务

前言 本节内容介绍如何使用docker容器来部署安装jenkins流水线服务。关于docker容器的安装本节内容不做介绍。请读者提前安装。 正文 ①使用docker查找jenkins官方镜像 ② 拉取jenkins官方镜像jenkins/jenkins&#xff0c;选择一个最新稳定版本&#xff0c;避免一些插件不兼…

15.一种坍缩式的简单——组合模式详解

当曾经的孩子们慢慢步入社会才知道&#xff0c;那年味渐淡的春节就像是疾驰在人生路上的暂停键。 它允许你在隆隆的鞭炮声中静下心来&#xff0c;瞻前顾后&#xff0c;怅然若失。 也允许你在寂静的街道上屏气凝神&#xff0c;倾听自己胸腔里的那团人声鼎沸。 孩子们会明白的&am…

mysql在服务器中的主从复制Linux下

mysql在服务器中的主从复制Linux下 为什么要进行主从复制主从复制的原理主从复制执行流程操作步骤主库创建从库创建 测试 为什么要进行主从复制 在业务中通常会有情况&#xff0c;在sql执行时&#xff0c;将表锁住&#xff0c;导致不能进行查询&#xff0c;这样就会影响业务的…

游戏平台如何定制开发?

随着科技的飞速发展和互联网的普及&#xff0c;游戏平台已成为人们休闲娱乐的重要选择。为了满足用户多样化的需求&#xff0c;游戏平台的定制开发显得尤为重要。本文将探讨游戏平台定制开发的过程、关键要素以及注意事项&#xff0c;为有志于涉足此领域的开发者提供参考。 一、…

商品评论接口的应用

一、应用场景 商家调研自家产品的满意度及改进建议&#xff0c;B端商户想要铺货挑选商品&#xff0c;独立站运营商 二、公共参数 请求地址: https://api/item_review 三、请求参数 请求参数&#xff1a;num_iid600530677643&data&page1 参数说明&#xff1a;参数…

OpenAI文生视频大模型Sora概述

Sora&#xff0c;美国人工智能研究公司OpenAI发布的人工智能文生视频大模型&#xff08;但OpenAI并未单纯将其视为视频模型&#xff0c;而是作为“世界模拟器” &#xff09;&#xff0c;于2024年2月15日&#xff08;美国当地时间&#xff09;正式对外发布。 Sora可以根据用户…

golang入门介绍-1

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

Maven depoly:Skipping artifact deployment

问题描述&#xff1a; 使用IDEA执行mvn depoly将本地开发的模块发布到Maven私服时&#xff0c;一直提示&#xff1a;Skipping artifact deployment&#xff0c;自动跳过了depoly部署阶段。 问题分析 Maven构建生命周期中的每一个阶段都是由对应的maven插件执行具体工作的。既然…

【无标题】旋转链表与力扣报错:member access within null pointer of type ‘struct ListNode‘

项目场景&#xff1a; 做单链表反转题目&#xff0c;报错&#xff1a;member access within null pointer of type ‘struct ListNode’ 题目链接:LINK 问题描述 我明明在初始化指针时候&#xff0c;已经处理了n2->next情况却依然报错 这个报错提示含义是&#xff1a;大概就…

C++日志库plog使用指南

前言 之前介绍过一个C语言日志库 轻量级c语言开源日志库log.c介绍&#xff0c;源代码只有不到200行&#xff0c;使用非常方便。但是也存在很多缺点&#xff0c;比如日志时间只支持打印到秒&#xff0c;没有作多线程处理&#xff0c;不支持日志回滚。在小型项目或者测试demo中使…

【Effective Objective - C】—— block 块

【Effective Objective - C】—— block 块 前言37.理解块的概念块的基础知识块可以捕获变量内联块的用法块的内部结构栈块堆块全局块要点 38.为常用的块类型创建typedef要点 39.用handler块降低代码分散程度协议传值实现异步块实现异步回调操作里的块要点 40.用块引用其所属对…

SpringBoot项目快速创建

SpringBoot项目快速创建 方法一&#xff1a;通过IDEA的Spring Initializr 点击文件&#xff0c;新建项目&#xff0c;选择Spring Initializr 名称&#xff1a;项目名称存放位置&#xff1a; 语言&#xff1a;Java类型&#xff1a;Maven组&#xff1a;<groupId>com.exam…

MyBatisPlus条件构造器和常用接口

前置配置文章 一、wapper介绍 wrapper的继承体系&#xff1a; Wrapper &#xff1a; 条件构造抽象类&#xff0c;最顶端父类 AbstractWrapper &#xff1a; 用于查询条件封装&#xff0c;生成 sql 的 where 条件 QueryWrapper &#xff1a; 查询条件封装UpdateWrapper &#x…

【MATLAB GUI】 4. 坐标区和表

看B站up主freexyn的freexyn编程实例视频教程系列36Matlab GUI的学习笔记 文章目录 坐标区表 坐标区 任务要求设计一个图像显示界面&#xff0c;根据选定的周期做出相应的sin函数图像 使用坐标区、弹出式菜单、普通按钮设计页面&#xff0c;弹出式菜单string设置为1、2、3、4代…

C#,动态规划(DP)丢鸡蛋问题(Egg Dropping Puzzle)的三种算法与源代码

1 扔鸡蛋问题 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;是运筹学的一个分支&#xff0c;是求解决策过程最优化的过程。20世纪50年代初&#xff0c;美国数学家贝尔曼&#xff08;R.Bellman&#xff09;等人在研究多阶段决策过程的优化问题时&#xf…

【MySQL】数据库概述

目录 一、为什么使用数据库&#xff1f; 二、数据库与数据库管理系统 2.1 相关概念 2.2 两者关系 三、 MySQL介绍 四、 RDBMS和非RDBMS 4.1 关系型数据库&#xff08;RDBMS&#xff09; 4.2 非关系型数据库&#xff08;非RDBMS&#xff09; 五、关系型数据库设计规则 …

WebService学习,wsdl文件详解

目录 第一章、起因1.1&#xff09;学习原因1.2&#xff09;提问的过程&#xff08;逐步提出问题&#xff09;1、&#xff1f;wsdl链接的含义&#xff0c;有什么作用&#xff1f;2、什么是wsdl文档&#xff1f;3、如何阅读wsdl文件&#xff1f;4、wsdl文件有什么作用&#xff1f…

百面嵌入式专栏(经验篇)如何在面试中介绍自己的项目经验

文章目录 1. 在面试前准备项目描述,别害怕,因为面试官什么都不知道2. 准备项目的各种细节,一旦被问倒了,就说明你没做过3.不露痕迹地说出面试官爱听的话4.一定要主动,面试官没有义务挖掘你的亮点5.一旦有低级错误,可能会直接出局6.引导篇:准备些加分点,在介绍时有意提到…

图文说明Linux云服务器如何更改实例镜像

一、应用场景举例 在学习Linux的vim时&#xff0c;我们难免要对vim进行一些配置&#xff0c;这里我们提供一个vim插件的安装包&#xff1a; curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o./install.sh && bash ./install.sh 但是此安装包…