代理模式笔记

news2024/11/15 21:44:24

代理模式

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

代理模式

代理模式在设计模式中是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="数学老师";

    @Override
    public 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("小明同学正在统计成绩");
    }


    @Override
    public 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="英语老师";
    @Override
    public 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类型的对象
     **/

    @Override
    public 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.coloradmin.cn/o/1463721.html

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

相关文章

使用单一ASM-HEMT模型实现从X波段到Ka波段精确的GaN HEMT非线性仿真

来源&#xff1a;Accurate Nonlinear GaN HEMT Simulations from X- to Ka-Band using a Single ASM-HEMT Model 摘要&#xff1a;本文首次研究了ASM-HEMT模型在宽频带范围内的大信号准确性。在10、20和30 GHz的频率下&#xff0c;通过测量和模拟功率扫描进行了比较。在相同的频…

【C++初阶】系统实现日期类

目录 一.运算符重载实现各个接口 1.小于 (d1)<> 2.等于 (d1d2) 3.小于等于&#xff08;d1<d2&#xff09; 4.大于&#xff08;d1>d2&#xff09; 5.大于等于&#xff08;d1>d2&#xff09; 6.不等于&#xff08;d1!d2&#xff09; 7.日期天数 (1) 算…

Nginx网络服务三-----(三方模块和内置变量)

1.验证模块 需要输入用户名和密码 我们要用htpasswd这个命令&#xff0c;先安装一下httpd 生成文件和用户 修改文件 访问页面 为什么找不到页面&#xff1f; 对应的路径下&#xff0c;没有这个文件 去创建文件 去虚拟机浏览器查看 有的页面不想被别人看到&#xff0c;可以做…

MongoDB的介绍和使用

目录 一、MongoDB介绍 二、MongoDB相关概念 三、MongoDB的下载和安装 四、SpringBoot 整合 MongoDB 一、MongoDB介绍 MongoDB是一种NoSQL数据库管理系统&#xff0c;采用面向文档的数据库模型。它以C语言编写&#xff0c;旨在满足大规模数据存储和高性能读写操作的需求。Mo…

BUGKU-WEB 文件包含

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 你说啥我就干啥&#xff1a;点击一下试试你会想到PHP伪协议这方面去嘛&#xff0c;你有这方面的知识储备吗&#xff1f; 相关工具 解题步骤 查看源码 看到了一点提示信息&#xff1a; ./index.…

【压缩感知基础】Nyquist采样定理

Nyquist定理&#xff0c;也被称作Nyquist采样定理&#xff0c;是由哈里奈奎斯特在1928年提出的&#xff0c;它是信号处理领域的一个重要基础定理。它描述了连续信号被离散化为数字信号时&#xff0c;采样的要求以避免失真。 数学表示 Nyquist定理的核心内容可以描述如下&…

06 内存管理

目录 c/c内存分布c语言中动态内存管理方式c中动态内存管理方式operator new与operator delete函数new和delete的实现原理定位new表达式(placement-new)常见题 1. c/c内存分布 看一段代码 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticV…

sql server想要小数点后向下取整怎么搞

select FORMAT(3.169, N2) as 四舍五入1, CAST(3.169 AS decimal(9,2)) as 四舍五入2, ROUND(3.169, 2) as 四舍五入3, CAST(FLOOR(3.169 * 100) / 100 AS decimal(9,2)) as 向下取整1, FLOOR(3.169 * 100) / 100 as 向下取整2, ceiling(3.169 * 100) / 100 as 向上取整—…

Studio One 6免费下载安装激活教程

一、Studio One 6安装 1.双击Studio One6安装包&#xff08;见文章尾部&#xff09;&#xff0c;如下图&#xff0c;可以切换语言&#xff0c;点击【OK】。 2.根据安装导航&#xff0c;点击【下一步】 3.阅读许可证协议后&#xff0c;点击【我接受】。 4.选择安装位置&#xf…

服务器开发

服务器开发涉及多个领域和技术&#xff0c;包括但不限于以下方面&#xff1a; 网络编程&#xff1a;服务器开发需要深入理解网络协议&#xff0c;如TCP/IP&#xff0c;UDP等&#xff0c;以及套接字编程。同时&#xff0c;对于常用的网络通信模型&#xff0c;如socket网络编程&…

Http改为Https后该如何测试

需要了解Http和Http之间的关系&#xff0c;他们之间都有哪些优点&#xff0c;哪些缺点&#xff0c;如果使用的产品进行了更改&#xff0c;该如何进行测试等等&#xff0c;Https提供了一个安全层&#xff08;SSL/TLS&#xff09;&#xff0c;这个安全层在客户端和服务器之间提供…

Nginx-----------高性能的 Web服务端 nginx编译安装 、平滑升级(一)

一、Nginx高性能的 Web服务端 Nginx是由1994年毕业于俄罗斯国立莫斯科鲍曼科技大学的同学为俄罗斯rambler.ru公司开发的&#xff0c;开发工作最早从2002年开始&#xff0c;第一次公开发布时间是2004年10月4日&#xff0c;版本号是0.1.02019年3月11日F5与NGINX达成协议,F5 将收购…

BLUEZ学习笔记_GATT_server_client_简单解析

文章参考了以下内容 蓝牙bluez5的开发方法及入门教程_bluez蓝牙配网demo-CSDN博客文章浏览阅读1w次&#xff0c;点赞15次&#xff0c;收藏99次。1 摘要这篇文章的主要目的是告诉大家应该如何使用bluez进行开发&#xff0c;由于bluez的文档实在太少了&#xff0c;入门门槛实在太…

【鸿蒙 HarmonyOS 4.0】TypeScript开发语言

一、背景 HarmonyOS 应用的主要开发语言是 ArkTS&#xff0c;它由 TypeScript&#xff08;简称TS&#xff09;扩展而来&#xff0c;在继承TypeScript语法的基础上进行了一系列优化&#xff0c;使开发者能够以更简洁、更自然的方式开发应用。值得注意的是&#xff0c;TypeScrip…

如何在群辉7.2中使用Docker搭建容器魔方服务并远程访问【内网穿透】

文章目录 1. 拉取容器魔方镜像2. 运行容器魔方3. 本地访问容器魔方4. 群辉安装Cpolar5. 配置容器魔方远程地址6. 远程访问测试7. 固定公网地址 本文主要介绍如何在群辉7.2版本中使用Docker安装容器魔方&#xff0c;并结合Cpolar内网穿透工具实现远程访问本地网心云容器魔方界面…

4核8G服务器能承受多少并发?

腾讯云4核8G服务器能承受多少并发&#xff1f;阿腾云的4核8G服务器可以支持20个访客同时访问&#xff0c;关于4核8G服务器承载量并发数qps计算测评&#xff0c;云服务器上运行程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#x…

useRef有什么用?

看一下官网定义 useRef是一个React Hook&#xff0c;它能帮助引用一个不需要渲染的值 这句话透露出一个信息&#xff0c;不需要渲染的值可以用useRef引用&#xff0c;那需要渲染的值用什么引用呢&#xff1f;当然是useState了&#xff0c;需要渲染的值指的就是状态嘛&#xff0…

基于springboot+vue的电影评论网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

一款高输出电流 PWM 转换器

一、产品描述 TPS543x 是一款高输出电流 PWM 转换器&#xff0c;集成了低电阻、高侧 N 沟道 MOSFET。具有所列的特性的基板上还包括高性能电压误差放大器&#xff08;可在瞬态条件下提供高稳压精度&#xff09;、欠压锁定电路&#xff08;用于防止在输入电压达到 5.5V 前启动&…

Spring Event 快速入门

请直接看原文 : Spring Event&#xff0c;贼好用的业务解耦神器&#xff01; (qq.com) -------------------------------------------------------------------------------------------------------------------------------- 前言 Spring Event 同步使用 Spring Event 异…