设计模式:使用最广泛的代理模式

需求场景

按着惯例,还是以一个应用场景作为代理模式的切入点。现在有一个订单系统,要求是:一旦订单被创建,只有订单的创建人才可以修改订单中的数据,其他人则不能修改。

基本实现思路

按着最直白的思路,就是查询数据库中订单的创建人和当前Session中的登录账号ID是否一致。

class Order {private String orderId;private String creatorId; // 订单创建者的IDprivate String details; // 订单详情// 省略其他属性和getter/setter方法public Order(String orderId, String creatorId, String details) {this.orderId = orderId;this.creatorId = creatorId;this.details = details;}// 其他业务逻辑...
}

系统修改

class OrderService {private Map<String, Order> orders = new HashMap<>();// 创建订单public void createOrder(String orderId, String creatorId, String details) {Order order = new Order(orderId, creatorId, details);orders.put(orderId, order);}// 修改订单public void modifyOrder(String orderId, String userId, String newDetails) {Order order = orders.get(orderId);if (order != null) {//检查是否拥有权限if(order.getCreateId().equals(userId)){order.setDetails(newDetails);}else{System.out.println("权限不足.");}} else {System.out.println("订单不存在.");}}// 其他业务逻辑...
}

该思路的问题

上述代码其实本身是没有问题的,也是Web贫血模式的常见实现思路,即在Service中通过大量的if else进行完成,如果非说问题的话,就是随着对于订单的操作越多Service代码会越发膨胀,例如,需求一开始是只要求改描述,下次又要求更改名称,下下次对于权限又细分等等,Service的modifyOrder就会增加很多的if else和set方法 ,扩展和维护十分的不优雅。或许下面的代理模式能提供一些能够优雅解决的新思路。

代理模式的定义

代理模式的核心定义是:为其他对象提供一种代理以此来控制对这个对象的访问。代理模式是以对象组合的方式对对象进行保护或者说功能扩展的一种方式。

代理模式结构

Sunject :目标接口,定义目标对象的具体操作。

Proxy:代理对象,实现与具体的目标对象一样的接口,这样就可以代理具体的目标对象。保存一个指向具体目标对象的引用,可以再需要的时候调用具体的目标对象,调用目标对象时进行控制和保护。

RealSubject:具体的目标对象,真正实现目标接口要求的功能

代码:

// 定义真实主题角色接口
interface Image {void display();
}// 实现真实主题角色
class RealImage implements Image {private String fileName;public RealImage(String fileName) {this.fileName = fileName;loadFromDisk(fileName);}@Overridepublic void display() {System.out.println("Displaying " + fileName);}// 模拟从磁盘加载图片资源private void loadFromDisk(String fileName) {System.out.println("Loading " + fileName + " from disk.");}
}// 定义代理主题角色
interface Proxy extends Image {void display();
}// 实现代理主题角色
class ProxyImage implements Proxy {private RealImage realImage;public ProxyImage(String fileName) {// 延迟加载RealImage对象this.realImage = null;}@Overridepublic void display() {if (realImage == null) {realImage = new RealImage("image.png");}realImage.display();}
}public class ProxyPatternDemo {public static void main(String[] args) {Proxy proxy = new ProxyImage("image.png");proxy.display();}
}

代理模式实现案例

相当于现在如果有了一个订单对象实例,那么就需要控制外部对它的访问,满足条件的可以访问,不满足条件的就不能访问。

使用代理模式来实现就是需要在Order对象之外再包一层对象,用于操作权限控制。本质上是一种保护代理思路。

 首先创建一个订单的操作接口

public interface OrderApi {String getId();String getName();String getDetails();String getCreatorId();void setId(String id);void setDetails(String details);void setName(String name);void setCreatorId(String creatorId);
}

 一个基本的订单实体类作为目标代理对象

class Order implements OrderApi {private String id;private String name;private String details;private String creatorId;public Order(String id, String name, String details, String creatorId) {this.id = id;this.name = name;this.details = details;this.creatorId = creatorId;}@Overridepublic String getId() {return id;}@Overridepublic String getName() {return name;}@Overridepublic String getDetails() {return details;}@Overridepublic String getCreatorId() {return creatorId;}@Overridepublic void setId(String id) {this.id = id;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic void setCreatorId(String creatorId) {this.creatorId = creatorId;}@Overridepublic void setDetails(String details) {this.details= details;}
}

实现一个代理对象

class OrderProxy implements OrderApi {private Order order;public OrderProxy(Order order) {this.order = order;}@Overridepublic String getId() {return order.getIdO();}@Overridepublic String getName() {return order.getNameO();}@Overridepublic String getDetails() {return order.getDetailsO();}@Overridepublic String getCreatorId() {return order.getCreatorIdO();}@Overridepublic void setId(String id) {// 在这里添加权限检查逻辑if (isCreator()) {order.setId(id);} else {throw new SecurityException("Only the creator can change the order ID.");}}@Overridepublic void setName(String name) {// 在这里添加权限检查逻辑if (isCreator()) {order.setName(name);} else {throw new SecurityException("Only the creator can change the order name.");}}@Overridepublic void setCreatorId(String creatorId) {// 创建者ID通常不允许更改throw new UnsupportedOperationException("Changing creator ID is not allowed.");}private boolean isCreator(String userId) {// 这里应该添加检查userId是否是订单的创建者// 为了示例简单,这里假设userId总是传入正确的,返回truereturn true;}
}

代理模式的理解

特点与分类

代理模式的本质是控制对象的访问。通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性。正是这个间接性,给了代理对象很多的活动空间。代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能。更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了,或是被替换掉了。
从实现上看,代理模式主要是使用对象的组合和委托,尤其是在静态代理的实现里面,会看得更清楚。但是也可以采用对象继承的方式来实现代理。这种实现方式在某些情况下,会比使用组合来的简单。还是以上面权限的例子,以继承的方式实现,首先剔除掉OrderApi

public class Order {//仅仅去除OrderApi的实现,其他的代码没有任何变化,就不再赘述了
}

代理对象:

public class OrderProxy extends Order {public OrderProxy(String productName, int orderNum, String orderUser) {super(productName, orderNum, orderUser);}public void setProductName(String productName, String user) {// 控制访问权限,只有创建订单的人员才能够修改if (user == null || user.equals(this.getOrderUser())) {super.setProductName(productName, user);} else {System.out.println("对不起, " + user + ",您无权修改订单中的产品名称。");}}public void setOrderNum(int orderNum, String user) {// 控制访问权限,只有创建订单的人员才能够修改if (user == null || user.equals(this.getOrderUser())) {super.setOrderNum(orderNum, user);} else {System.out.println("对不起, " + user + ",您无权修改订单中的订购数量。");}}public void setOrderUser(String orderUser, String user) {// 控制访问权限,只有创建订单的人员才能够修改if (user == null || user.equals(this.getOrderUser())) {super.setOrderUser(orderUser, user);} else {System.out.println("对不起, " + user + ",您无权修改订单中的订购人。");}}}

不同的代理类型,这种附加的间接性有不同的用途,也就具有不同的特点。

  • 远程代理:隐藏了一个对象存在于不同的地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象。从客户的角度来讲,它只是在使用代理对象而已。
  • 虚代理:可以根据需要来创建“大”对象,只有到必须创建对象的时候,虚代理才会创建对象,从而大大加快程序运行速度,并节省资源。通过虚代理可以对系统进行优化。
  • 保护代理:可以在访问一个对象的前后,执行很多附加的操作,除了进行权限控制之外,还可以进行很多跟业务相关的处理,而不需要修改被代理的对象。也就是说,可以通过代理来给目标对象增加功能。
  • 智能指引:和保护代理类似,也是允许在访问一个对象的前后,执行很多附加的操作,这样一来就可以做很多额外的事情,比如,引用计数等。

在这些代理类型中,最常见的是保护代理远程代理。上述的例子就是一个典型的保护代理的实现,即具体订单的操作是不变的,如果需要对订单的操作进行特殊处理,一切变动皆集中在代理对象中,代理对象对于订单对象起到了保护隔离的作用,同时代码层面上也承载了“频繁变化”的需求内容,将“变化”隔离出来,对于后续的需求扩展也是十分有效。

建议在如下情况中选用代理模式。

  • 需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理
  • 需要按照需要创建开销很大的对象的时候,可以使用虚代理
  • 需要控制对原始对象的访问的时候,可以使用保护代理
  • 需要在访问对象执行一些附加操作的时候,可以使用智能指引代理

具体目标和代理间的关系

Java中代理模式的应用

Java 对代理模式提供了内建的支持,在java.lang,refect 包下面,提供了一个 Proxy的类和一个InvocationHandler 的接口。
通常把前面自己实现的代理模式称为 Java 的静态代理。这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活。而使用Java内建的对代理模式支持的功能来实现则没有这个问题。
通常把使用 Java 内建的对代理模式支持的功能来实现的代理称为Java的动态代理,动态代理跟静态代理相比,明显的变化是:静态代理实现的时候,在Subject接口上定义很多的方法,代理类里面自然也要实现很多方法:而动态代理实现的时候,虽然Subicct接口上定义了很多方法,但是动态代理类始终只有一个invoke 方法。这样,当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。
Java的动态代理目前只能代理接口,基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。具体的内部实现细节这里不去讨论。如果要实现类的代理,可以使用cglib(一个开源的Code Generation Library)。

还是来看看示例,那就修改上面保护代理的示例,看看如何使用Java的动态代理来实现同样的功能。
(1)订单接口的定义是完全一样的,就不再赘述了。
(2)订单对象的实现,只是添加了一个 toString,,以方便测试输出,这里也不去示例了。在前面的示例中,toString已实现在代理类里面了。
(3)直接看看代理类的实现,大致有如下变化。

  • 要实现InvocationHandler接口。
  • 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好过后,返回被代理的目标对象的接口,以利于客户端的操作。
  • 需要实现 invoke 方法,在这个方法里面,具体判断当前是在调用什么方法,需要如何处理。
import java.lang.reflect.*;/*** 使用Java中的动态代理*/
public class DynamicProxy implements InvocationHandler {// 被代理的对象private OrderApi order;/*** 获取绑定好代理和具体目标对象后的目标对象的接口* @param order 具体的订单对象,相当于具体目标对象* @return 绑定好代理和具体目标对象后的目标对象的接口*/public OrderApi getProxyInterface(Order order) {// 设置被代理的对象,方便invoke里面的操作this.order = order;// 把真正的订单对象和动态代理关联起来OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(order.getClass().getClassLoader(),order.getClass().getInterfaces(),this);return orderApi;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 如果是调用setter方法就需要检查权限if (method.getName().startsWith("set")) {// 假设Order类有一个getOrderUser()方法来获取订单创建者的用户ID// 并且order.getOrderUser().equals(args[0])来检查是否是创建者if (order.getOrderUser() != null && order.getOrderUser().equals(args[0])) {// 可以操作return method.invoke(order, args);} else {// 如果不是创建者,不能修改System.out.println("对不起," + args[0] + ",您无权修改本订单中的数据");}} else {// 不是调用的setter方法就继续运行return method.invoke(order, args);}return null;}
}

使用时则是:

public class Client {public static void main(String[] args) {// 张三先登录系统创建了一个订单Order order = new Order("XXX", 100, "张三");// 创建一个动态代理DynamicProxy dynamicProxy = new DynamicProxy();// 然后把订单和动态代理关联起来OrderApi orderApi = dynamicProxy.getProxyInterface(order);// 以下就需要使用被代理过的接口来操作了// 李四想要来修改,那就会报错orderApi.setOrderNum(123, "李四");// 输出orderSystem.out.println("李四修改后订单记录没有变化:" + orderApi);// 张三修改就不会有问题orderApi.setOrderNum(123, "张三");// 再次输出orderSystem.out.println("张三修改后,订单记录:" + orderApi);}
}

代理在Java中的使用十分常见,例如Spring中的AOP,其本质就是代理模式。

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

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

相关文章

钡铼分布式 IO 系统 OPC UA边缘计算耦合器BL205

深圳钡铼技术推出的BL205耦合器支持OPC UA Server功能&#xff0c;以服务器形式对外提供数据。符合IEC 62541工业自动化统一架构通讯标准&#xff0c;数据可以选择加密&#xff08;X.509证书&#xff09;、身份验证方式传送。 安全策略支持basic128rsa15、basic256、basic256s…

01 MySQL

学习资料&#xff1a;B站视频-黑马程序员JavaWeb基础教程 文章目录 JavaWeb整体介绍 MySQL1、数据库相关概念2、MySQL3、SQL概述4、DDL:数据库操作5、DDL:表操作6、DML7、DQL8、约束9、数据库设计10、多表查询11、事务 JavaWeb整体介绍 JavaWeb Web&#xff1a;全球广域网&…

ELK日志收集之多文件提取文件名和日志时间

需求&#xff1a;多个设备的日志同时保存在一台服务器上&#xff0c;日志文件的文件名是设备的ID&#xff0c;需要将多个文件提取文件名作为最终的筛选字段&#xff0c;同时提取日志中的时候日期时间替换系统的timestamp filebeat配置&#xff1a; filebeat.inputs:- type:…

S参数入门

一、说明 S参数全称为散射参数&#xff0c;主要用来作为描述线性无源互联结构的一种行为模型&#xff0c;来源于网络分析方法。网络分析法是一种频域方法&#xff0c;在一组离散的频率点上&#xff0c;通过在输入和输出端口得到的参量完全描述线性时不变系统&#xff08;定义参…

【cocos creator】ts中export的模块管理

在 TypeScript&#xff08;TS&#xff09;中&#xff0c;export 和 import 的概念与 Java 中的 public 类、接口以及 import 语句有一些相似之处。可以用以下方式来类比理解&#xff1a; Export 在 TypeScript 中&#xff0c;export 用于将模块中的变量、函数、类等暴露给外部…

AI 应用还没有大量出现,缺什么?缺聊天机器人编程语言 | Chatopera

只有帮助人发挥创意的才是大市场 现在是需要大量的 AI 应用了。如何产生大量的 AI 应用呢&#xff1f;当年乔布斯说&#xff0c;他看到了个人电脑的两个趋势&#xff0c;一个是图形化用户界面&#xff0c;一个是面向对象编程语言。今天&#xff0c;AI 应用也是新的【图形用户界…

【hadoop大数据集群 2】

【hadoop大数据集群 2】 文章目录 【hadoop大数据集群 2】1. 虚拟机克隆2. 时间同步3. 环境变量配置、启动集群、关闭集群 1. 虚拟机克隆 克隆之后一定要重新生成新虚拟机唯一的MAC地址和UUID等&#xff0c;确保新虚拟机与源虚拟机在网络拓扑中不发生冲突。 注意1.生成新的MA…

什么是股指期货交割?股指期货交割的例子

股指期货交割是指在股指期货合约到期时&#xff0c;投资者需要按照合约规定完成的结算过程。与一般的商品期货、国债期货或外汇期货不同&#xff0c;股指期货采用的是现金交割方式。 股指期货交割的方式 【现金交割】股指期货的交割不需要实际交割一篮子股票指数成分股。相反…

【ARMv8/v9 GIC- 700 系列 2 -- GIC-700 上电控制寄存器 GICR_PWRR】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC-700 上电GICR_PWRR 寄存器字段介绍GICR_PWRR 功能说明GICR_PWER 代码配置GICR_PWRR 使用场景GICR_PWRR 注意事项GIC-700 上电 GICR_PWRR(功耗寄存器)是ARM GICv4架构中用于控制GIC-700是否可以关闭电源的寄存器。它通过几个位…

农田一体化闸门:渠道自动化与精准测量

在农业现代化的浪潮中&#xff0c;农田灌溉系统的智能化升级已成为提升农业生产效率、节约水资源的关键。农田一体化闸门作为这一升级过程中的重要一环&#xff0c;以其全面自动化控制与精准测量的能力&#xff0c;为农田灌溉带来了变化。 农田一体化闸门系统集传感技术、自动化…

昇思25天学习打卡营第5天 | 数据集

在探索MindSpore深度学习框架中的数据集处理过程&#xff0c;我对其数据加载和处理流程有了深入的了解。MindSpore提供了一套功能强大的工具&#xff0c;可以有效地处理和转换数据&#xff0c;确保了数据预处理的效率和质量。以下是我从本次学习中得到的几点主要心得&#xff1…

1219:马走日

#include<bits/stdc.h> using namespace std; int vis[8][2]{-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,-2,-2,-1};//构造偏移量数组 int t,n,m,x,y,ans;//棋盘总共由(n)(m)个点 bool st[100][100];//如果st[i][j]0 表示i,j这个坐标没有走过 st[a][b]1表示a,b这个坐标走过 void d…

多层全连接神经网络(三)---分类问题

问题介绍 机器学习中的监督学习主要分为回归问题和分类问题&#xff0c;我们之前已经讲过回归问题了&#xff0c;它希望预测的结果是连续的&#xff0c;那么分类问题所预测的结果就是离散的类别。这时输入变量可以是离散的&#xff0c;也可以是连续的&#xff0c;而监督学习从数…

模型训练中出现loss为NaN怎么办?

文章目录 一、模型训练中出现loss为NaN原因1. 学习率过高2. 梯度消失或爆炸3. 数据不平衡或异常4. 模型不稳定5. 过拟合 二、 针对梯度消失或爆炸的解决方案1. 使用torch.autograd.detect_anomaly()2. 使用 torchviz 可视化计算图3. 检查梯度的数值范围4. 调整梯度剪裁 三、更具…

Nginx的核心功能

1. Nginx的核心功能 1.1 nginx反向代理功能 正向代理 代理的为客户端&#xff0c;对于服务器不知道真实客户的信息。例如&#xff1a;翻墙软件 反向代理服务器 代理的为服务器端。对于客户来说不知道服务器的信息。例如&#xff1a;nginx 项目部署图 web项目部署的虚拟机和Ng…

Spring MVC-什么是Spring MVC?

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 1.MVC定义2. Spring MVC 官方对于Spring Web MVC的描述这样的: Spring Web MVC is the original web framework built on the Servlet APl and has been includedin the Spring Frame…

pdf提取其中一页怎么操作?提取PDF其中一页的方法

pdf提取其中一页怎么操作&#xff1f;需要从一个PDF文件中提取特定页码的操作通常是在处理文档时常见的需求。这种操作允许用户选择性地获取所需的信息&#xff0c;而不必操作整个文档。通过选择性提取页面&#xff0c;你可以更高效地管理和利用PDF文件的内容&#xff0c;无论是…

PyTorch 深度学习实践-循环神经网络基础篇

视频指路 参考博客笔记 参考笔记二 文章目录 上课笔记基于RNNCell实现总代码 基于RNN实现总代码 含嵌入层的RNN网络嵌入层的作用含嵌入层的RNN网络架构总代码 其他RNN扩展基本注意力机制自注意力机制&#xff08;Self-Attention&#xff09;自注意力计算多头注意力机制&#xf…

PyQt5中pyqtgraph鼠标获取坐标

PyQt5中pyqtgraph鼠标获取坐标 1、效果 2、流程 安装库: pip install numpy==1.19.5 pip install PyQt5==5.15.9 pip install pyqtgraph==0.11.11、创建一个ui 2、在ui中添加一个Vertical Layout控件,命名为my_view 3、把ui转成py 4、绑定鼠标移动事件 5、x,y值向下取整 6…

【QT开发(19)】2023-QT 5.14.2实现Android开发,使用新版SDK,试图支持 emulator -avd 虚拟机

之前的博客【QT开发&#xff08;17&#xff09;】2023-QT 5.14.2实现Android开发&#xff0c;SDK是24.x版本的&#xff0c;虚拟机是32位的&#xff0c;但是现在虚拟机是64位的了&#xff0c;需要升级SDK匹配虚拟机 文章目录 最后的效果1.1 下载最新版 SDK tools (仅限命令行工…