设计模式之结构型模式---代理模式

news2024/9/28 3:25:31

目录

  • 1.概述
  • 2.代理模式类图
  • 3.应用场景
    • 3.1 功能增强
    • 3.2 控制访问
  • 4.实现
    • 4.1 静态代理的实现
      • 4.1.1 实现静态代理模式的步骤
      • 4.1.2 静态代理的缺点
    • 4.2 动态代理的实现
      • 4.2.1 Java JDK 动态代理的实现
        • Method
        • InvocationHandler
        • Proxy
      • 4.2.2 JDK动态代理的使用方法
      • 4.2.3 JDK动态代理的实现步骤

1.概述

代理模式是结构型模式的一种,结构型模式描述的是如何将类和对象按照某种布局组成更大的结构,它分为类结构型和对象结构型,类结构型主要都会哦那个继承机制来组织接口和类,而对象结构型主要是使用组合和聚合来组合对象。代理模式分为两种,分别是静态代理和动态代理,代理模式主要是为其他对象提供一种代理以控制对这个对象的访问,如下所示:

在这里插入图片描述
如上图所示,假设我们客户端A想访问目标C,但是无法直接访问,我们就可以使用一个代理B代替我们去访问目标C,比如我们想买一个国外的商品,这个商品在国内买不到,而我们又没有时间去国外,或者是语言不通,这时候我们会请代购帮我们买,这里的客户端A指的就是我们自己,代理就是代购,目标C就是我们要买的商品。

为了更加浅显地展示代理模式的原理和实现,参考B站上一个博主的买U盘的例子来介绍本文,博主名字具体忘了,大家想看视频教学的可以到B站搜索“代理模式”关键词学习。

2.代理模式类图

在这里插入图片描述


抽象主题类: 上图中的interface 接口,在这个接口中定义我们想要做的事情,我们使用doSomeThing()表示我们想要做的事情。这里官方点说就是,通过接口或者抽象类声明真实主题和代理对象实现的业务方法。
真实主题类 : 上图中的RealSubject,实现了抽象主题类中的具体业务。
代理类: 上图中的Proxy, 实现了与真实主题类相同的接口,它包含对真实主题类的引用,可以控制访问或者是扩展真实主题的功能

3.应用场景

3.1 功能增强

代理模式的应用场景特别多,有一段时间,Android的插件化功能就需要用到代理模式,简单的说就是通过继承系统的某个类,然后重写我们想要操作的方法,使用动态代理,让系统调用我们实现的类,以完成我们想要添加的功能,所以代理模式可以做功能增强,也就是说可以在现有的功能基础上,增加额外的功能。如下图所示:
在这里插入图片描述
功能增强意思就如上图所示,比如有一个原始功能类定义了码农只能搬砖(写代码),但是后面码农升级了,在上班之前可以去送几单外卖,下班之后可以去开滴滴。如果码农直接去使用原始功能类,那么增加这些功能就得修改原始功能类,不符合开闭原则;但是使用代理类就可以在之前原始功能类的基础上,增加新的功能,这就是代理模式的功能增强

3.2 控制访问

控制访问也就是我们可以使用代理类去控制客户端类访问目标,防止客户端类带有危险请求导致目标类受到破坏,如下图所示:

在这里插入图片描述

4.实现

实现代理模式的方式有两种,分别是静态代理和动态代理,在这里我们使用文章开头的卖U盘的例子实现,我们模拟用户购买U盘的行为:
首先用户是客户端类,进行购买U盘的行为,商家是代理类,代理某个品牌的U盘,而厂家是目标类,表示卖U盘这个行为。三者的关系如下所示:

在这里插入图片描述
如上图所示,商家和厂家的目的都是卖U盘,他们完成的功能是一致的,都是卖U盘这个行为,下面我们一起来看静态代理和动态代理两种模式的实现

4.1 静态代理的实现

静态代理的代理类是自己手工实现的,我们自己创建一个Java类表示代理类,同时要代理的目标是确定的,静态代理的特点就是实现简单,容易理解。

4.1.1 实现静态代理模式的步骤

(1)创建接口,定义卖U盘的方法,表示厂家和商家要做的事情,即卖U盘

public interface IUSBShop {
     int sellUSB();
}

(2)创建厂家类,实现步骤(1)的接口

public class USBFactory implements IUSBShop {
    @Override
    public int sellUSB() {
        return 100;//返回100表示厂家的U盘定价
    }
}

(3)创建商家类,也就是代理类,实现步骤(1)的接口,实现卖U盘的方法,并持有一个厂家类的引用

public class USBProxy implements IUSBShop {
    private IUSBShop mIUSBShop;

    public USBProxy(IUSBShop iusbShop){
        this.mIUSBShop = iusbShop;
    }
    @Override
    public int sellUSB() {
        int price = mIUSBShop.sellUSB();//厂家建议零售价
        System.out.println("厂家建议的零售价是:" + price);
        //功能增强,商家的定价
        int finalPrice = price + 40;
        return finalPrice;
    }
}

如上面代码所示代理类在拿到厂家的定价后,扩展定义了自己家的定价

(4)创建客户端类,调用商家的方法购买U盘

public class Client {
    public static void main(String[] args) {
        IUSBShop iusbShop = new USBFactory();
        USBProxy usbProxy = new USBProxy(iusbShop);
        System.out.println("商家不接受厂家建议,最终卖一个USB的价格是:"+usbProxy.sellUSB());
    }
}

4.1.2 静态代理的缺点

使用静态代理时,假设项目中的需要代理的目标类很多时,代理类可能会需要成倍的增加,也就是说,增加一个目标类就得手动增加一个代理类与之对应,而且假设接口中的功能增加或者是修改了,会影响众多的实现类,即厂家类代理类都需要修改。而解决这些的办法就是使用动态代理

4.2 动态代理的实现

动态代理是指在程序执行的过程中,使用JDK的反射机制创建代理类的对象,并动态指定要代理的目标类,动态代理的实现方式有两种,一是使用JDK的动态代理(本文的实现方式)即使用Java发射包中的类和接口实现动态代理的功能,在Java的反射包java.lang,reflect中,使用InvocationHandler,Method,Proxy三个类实现动态代理,第二种就是使用CgLib实现动态代理

当静态代理中目标类很多时,就可以使用动态代理,因为动态代理即使目标类很多,代理类的数量也可以很少,而且当修改了接口中的方法时,不会影响代理类。

注释:CgLib(Code Generation Librart)是实现动态代理的第三方工具库,它的原理是继承,CbLib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改,因为CgLib是继承重写方法,所以要求目标类不能是final的,如Mybatis Spring中都有使用导CgLib

4.2.1 Java JDK 动态代理的实现

在使用JDK的动态代理之前,我们需要了解三个类,即Method,InvocationHandler,Proxy.

Method

代表类中的方法,method中有一个invoke()方法,表示方法的调用,invoke方法需要2个参数:
1.object: 表示需要执行的方法所属的对象
2.object args :执行方法时需要的参数值

InvocationHandler

这个类中就一个invoke方法,这个方法表示代理对象要执行的功能代码,代理类要完成的功能写在这个invoke方法中,就如我们前面说的代理类主要完成的作用就是目标方法的调用和功能的扩展增强,这些逻辑就是写在这个类的invoke()方法中

invoke方法原型如下:

invoke(Object proxy, Method method, Object[] args)

参数解释:
(1)proxy:Jdk创建的代理对象,不需要赋值
(2)method: 目标类的方法,由JDK提供
(3)args: 目标类方法中的参数

Proxy

这个类作用比较简单,作用就是创建代理对象,在该类中有一个用于创建代理类对象的方法newProxyInstance()方法原型如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

参数解释:
(1)ClassLoader:目标对象的类加载器,使用反射获取
(2)interface:目标对象实现的接口,反射获取
(3)InvocationHandler: 我们自己实现的,代理类要完成的功能

这个方法的返回值就是一个代理对象

4.2.2 JDK动态代理的使用方法

(1)创建一个类实现Invocationhandler,重写invoke()方法,把要完成的功能写在此方法中
(2)在我们重写的invoke()方法中有个Method类型的参数,这里就是表示目标类中的方法,我们可以通过Method执行某个目标类中的方法,使用method.invoke(目标对象,方法参数)
(3)使用Proxy类创建代理对象,调用目标类的方法完成定义的功能。

4.2.3 JDK动态代理的实现步骤

我们还是以商家卖U盘为例介绍JDK动态代理的实现步骤
(1)创建接口定义目标类要完成的功能

public interface IUSBShop {
     int sellUSB();
}

(2)创建目标类实现步骤(1)中创建的接口

public class USBFactory implements IUSBShop {
    @Override
    public int sellUSB() {
        return 100;
    }
}

(3) 创建一个类实现InvocationHandler接口,在该接口的invoke()方法中完成代理类的功能,即调用目标方法,增强功能

public class MyInvocationHandler implements InvocationHandler {
    private Object usbFactory;

    public void setUSBFactory(Object usbFactory) {
        this.usbFactory = usbFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int price = (int) method.invoke(usbFactory, args);
        System.out.println("厂家零售价: " + price);
        int finalprice = price + 40;
        System.out.println("最终价格: " + finalprice);

        return finalprice;
    }
}

(4)使用Proxy类的静态方法,创建代理对象,并把返回值转为接口类型,调用目标类方法,完成买U盘的行为

public class Client {
    public static void main(String[] args) {
        IUSBShop iusbShop = new USBFactory();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        myInvocationHandler.setUSBFactory(iusbShop);
        IUSBShop proxy = (IUSBShop) Proxy.newProxyInstance(USBFactory.class.getClassLoader(),
                iusbShop.getClass().getInterfaces(), myInvocationHandler
        );

        System.out.println("FinalResult :" + proxy.sellUSB());
    }
}

动态代理可以在不改变原来目标方法功能的前提下,在代理中增强扩展自己的代码

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

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

相关文章

本内容由【想发APP】创始人编写:uniAPP申请百度广告后,开发者必须要空包签名验证应用的所有权,实现的详细内容如下

Android获取签名 注意先安装好jdk和jre&#xff0c;然后配置好环境变量 说明 空包签名需要本地配置jdk环境 Windows 安装JDK及环境变量的配置 命令行方式 命令格式&#xff1a; jarsigner -verbose -keystore [签名文件路径] -signedjar [签名后apk的文件路径] [未签名apk的文…

对顶堆学习笔记

对顶堆学习笔记 文章目录 **对顶堆学习笔记**介绍例题洛谷 P1168 中位数code 介绍 对顶堆是由一个大顶堆和一个小顶堆组合而成的数据结构&#xff0c;与传统堆维护最大数不同&#xff0c;对顶堆用于动态维护第k大的数。 对于对顶堆&#xff0c;我们可以用两个优先队列来表示两…

IllegalArgumentException: OnNoRibbonDefaultCondition异常与Maven Helper插件解决jar包冲突

在搭建Spring Cloud项目时&#xff0c;引入了不同版本的jar&#xff0c;导致项目启动时报错: main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.Bea…

Python 制作英文学习词典(简易版)

前言 今日分享 Python 制作英文学习词典&#xff0c;大家都可以尝试。 题目 制作英文学习词典。编写程序制作英文学习词典&#xff0c;词典有3个基本功能&#xff1a;添加、查询和退出。程序读取源文件路径下的txt格式词典文件&#xff0c;若没有就创建一个。词典文件存储方…

arcgis js 4.x加载自定义坐标系的arcgis server发布的wms服务

一、问题描述 一般城市本身用的都是自己定义的城市&#xff08;本地&#xff09;坐标系&#xff0c;没有对应公开的EPSG代码&#xff0c;如下图如果直接加载自定义坐标系的wms服务&#xff0c;直接会报错。 var customProjLayer new WMSLayer({url: "http://10.1.8.37:6…

封装一个带el-form的,带el-table的,带分页的,带搜索查询的dialog组件,很使用的二次封装组件。

#封装dialog小案例 提示&#xff1a;这是我工作中封装的代码&#xff0c;很使用&#xff0c;需要的可以拿去&#xff0c; 在我们的代码中往往会出现点击按钮出现弹窗进行操作&#xff0c;那么我们就需要对dialog进行一个二次封装。 下边是大概的一个样式。 ##对组件进行二次…

C语言中一维数组及二维数组的运用

1、int arr[] {12,3,4,5,6}; int * p arr; int * q &arr[1]; 其中arr是数组名&#xff0c;代表了整个数组的首元素地址&#xff0c;这个是一个常量&#xff0c;放在常量存储区&#xff0c;所以在给int*p赋值的时候可以不用带&&#xff0c;而下面的arr[1]则代表数…

unidbg或者java层解密方法IDEA中打包成jar包供python调用方法

一、导出jar包方法 &#xff08;1&#xff09;配置jar包参数 &#xff08;2&#xff09;创建生成jar包 成功生成&#xff01; 二、Python代码调用 import jpypejvmPath jpype.getDefaultJVMPath() d unidbg-android.jar # 对应jar地址 jpype.startJVM(jvmPath, "-ea&q…

cloudflared tunnel后台启动

前期视频链接 先前台启动&#xff0c;本文参考 cloudflared tunnel --url localhost:88 run ruoyu 或者 cloudflared tunnel --url localhost:88 --no-chunked-encoding run ruoyu找到你的uuid.json 文件 创建你的config.yaml 或config.yml 内容如下 uuid 配置自己的 url: …

创建员工表并按要求完成数据

mysql> create table employee( id int primary key, name varchar(50), gender enum(男,女) default 男, salary int ); Query OK, 0 rows affected (0.01 sec)mysql> mysql> desc employee; ------------------------------------------------------ | Field | T…

spring重点标签有哪些

<bean>标签 id属性:在容器中Bean实例的唯一标识&#xff0c;不允许重复 class属性:要实例化的Bean的全限定名 scope属性:Bean的作用范围&#xff0c;常用是singleton(默认)和prototype <property>标签:属性注入 name属性:属性名称 value属性:注入的普通属性值 ref属…

Perfetto概览

1、Perfetto 简介 Perfetto是谷歌提供的一个用于性能检测和 trace 文件抓取分析的生产级开源工具包。它可以帮助我们进行应用层面以及系统层面的 trace 文件抓取、native/java 内存剖析。它还提供了一个强大的库能够帮助我们在 web 网页上对抓取的trace 文件进行可视化分析&am…

java项目之农产品供销服务系统(ssm+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的农产品供销服务系统。 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&#xff1a;ssm&#xff0c;mybatis JDK…

关于chrome禁止混合内容造成的问题

混合内容 chrome 对混合内容的定义 是指 存储于http协议下的静态资源与https协议下的资源混用 Chrome官方相关说明 chrome 通过安全策略将 http强制转换为https 这会导致存储于http协议下的 文件出现协议问题从而无法访问 例&#xff1a; 从http文件服务器中引用一张图片到配…

LinearAlgebraMIT2_1_TheColumnSpaceOfAContainsAllVectorsAx

对于Axb&#xff0c;我们要迅速想到线性组合&#xff0c;如下&#xff0c; 我们也可以使用rank秩来反应一个矩阵的特征&#xff0c;如果满秩则可以表示整个向量空间&#xff0c;如上只有两个independent column&#xff0c;它的rank2&#xff0c;表达一个plane平面。 接下来我…

一款玩法十分经典的RPG游戏《QQ宠物大乐斗2》

文章目录 介绍界面介绍游戏主界面&#xff1a;出城界面&#xff1a; 战斗系统主动战斗被动战斗 技能系统主动技能被动技能五神技狂龙傲天拳惊天混元掌灵气寒霜指道威无极真气八卦迷踪腿 道具系统装备系统装备品质装备展示装备获得方式 玩家升级系统通过升级增加属性点使用道具更…

MySQL复习指南

1.数据类型 【数值型】 int或者integer&#xff0c;大小为4个字节&#xff0c;范围&#xff1a;&#xff08;大约&#xff09;-21.5亿~21.5亿&#xff1b; bigint&#xff0c;大小为8个字节&#xff1b; float大小为4个字节&#xff1b; double大小为8个字节&#xff1b; …

为什么要做海外应用市场排名

谷歌和苹果iOS应用商店的算法关键指标&#xff0c;都是基于确保应用商店推荐的应用可以满足大部分用户的需求。基于此&#xff0c;应用覆盖的关键词&#xff0c;应用评分和评论&#xff0c;和应用获取的下载量所体现的用户体验数据&#xff0c;对我们的应用能否获得商店高排名都…

Ajax简介和实例

目录 什么是 AJAX &#xff1f; AJAX实例 ajax-get无参 ajax-get有参 对象和查询字符串的互转 ajax-post ajax-post 表单 AJAX 是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。 什么是 AJAX &#xff1f; 菜鸟教程是这样介绍的&#xff1a…

MySQL数据修改和插入数据

1.创建表&#xff1a; 创建员工表employee&#xff0c;字段如下&#xff1a; id&#xff08;员工编号&#xff09;&#xff0c;name&#xff08;员工名字&#xff09;&#xff0c;gender&#xff08;员工性别&#xff09;&#xff0c;salary&#xff08;员工薪资&#xff09; …