11.3 模式讲解
11.3.1 认识代理模式
(1)代理模式的功能
代理模式是通过创建一个代理对象,用这个代理对象去代表真实的对象,客户端得到这个代理对象过后,对客户端没有什么影响,就跟得到了真实对象一样来使用。
当客户端操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真正的对象。
正是因为有代理对象夹在客户端和被代理的真实对象中间,相当于一个中转,那么在中转的时候就有很多花招可以玩,比如:判断一下权限,如果没有足够的权限那就不给你中转了,等等。
(2)代理的分类
事实上代理又被分成多种,大致有如下一些:
- 虚代理:根据需要来创建开销很大的对象,该对象只有在需要的时候才会被真正创建
- 远程代理:用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以是在本机,也可以在其它机器上,在Java里面最典型的就是RMI技术
- copy-on-write代理:在客户端操作的时候,只有对象确实改变了,才会真的拷贝(或克隆)一个目标对象,算是虚代理的一个分支
- 保护代理:控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问
- Cache代理:为那些昂贵的操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
- 防火墙代理:保护对象不被恶意用户访问和操作
- 同步代理:使多个用户能够同时访问目标对象而没有冲突
- 智能指引:在访问对象时执行一些附加操作,比如:对指向实际对象的引用计数、第一次引用一个持久对象时,将它装入内存等
在这些代理类型中,最常见的是:虚代理、保护代理、远程代理和智能指引这几种。本书主要讨论和示例了虚代理和保护代理,这是实际开发中使用频率最高的。
对于远程代理,没有去讨论,因为在Java中,远程代理的典型体现是RMI技术,要把远程代理讲述清楚,就需要把RMI讲述清楚,这不在本书讨论范围之内。
对于智能指引,基本的实现方式和保护代理的实现类似,只是实现的具体功能有所不同,因此也没有具体去讨论和示例。
(3)虚代理的示例
前面的例子就是一个典型的虚代理的实现。
起初每个代理对象只有用户编号和姓名的数据,直到需要的时候,才会把整个用户的数据装载到内存中来。
也就是说,要根据需要来装载整个UserModel的数据,虽然用户数据对象是前面已经创建好了的,但是只有用户编号和姓名的数据,可以看成是一个“虚”的对象,直到通过代理把所有的数据都设置好,才算是一个完整的用户数据对象。
(4)copy-on-write
拷贝一个大的对象是很消耗资源的,如果这个被拷贝的对象从上次操作以来,根本就没有被修改过,那么再拷贝这个对象是没有必要的,白白消耗资源而已。那么就可以使用代理来延迟拷贝的过程,可以等到对象被修改的时候才真的对它进行拷贝。
copy-on-write可以大大降低拷贝大对象的开销,因此它算是一种优化方式,可以根据需要来拷贝或者克隆对象。
(5)具体目标和代理的关系
从代理模式的结构图来看,好像是有一个具体目标类就有一个代理类,其实不是这样的。如果代理类能完全通过接口来操作它所代理的目标对象,那么代理对象就不需要知道具体的目标对象,这样就无须为每一个具体目标类都创建一个代理类了。
但是,如果代理类必须要实例化它代理的目标对象,那么代理类就必须知道具体被代理的对象,这种情况下,一个具体目标类通常会有一个代理类。这种情况多出现在虚代理的实现里面。
(6)代理模式调用顺序示意图
代理模式调用顺序如图11.4所示:
图11.4 代理模式调用顺序示意图
11.3.2 保护代理
保护代理是一种控制对原始对象访问的代理,多用于对象应该有不同的访问权限的时候。保护代理会检查调用者是否具有请求所必需的访问权限,如果没有相应的权限,那么就不会调用目标对象,从而实现对目标对象的保护。
还是通过一个示例来说明。
1:示例需求
现在有一个订单系统,要求是:一旦订单被创建,只有订单的创建人才可以修改订单中的数据,其他人不能修改。
相当于现在如果有了一个订单对象实例,那么就需要控制外部对它的访问,满足条件的可以访问,而不满足条件的就不能访问了。
2:示例实现
(1)订单对象的接口定义
要实现这个功能需要,先来定义订单对象的接口,很简单,主要是对订单对象的属性的getter/setter方法,示例代码如下:
/** * 订单对象的接口定义 */ public interface OrderApi { /** * 获取订单订购的产品名称 * @return 订单订购的产品名称 */ public String getProductName(); /** * 设置订单订购的产品名称 * @param productName 订单订购的产品名称 * @param user 操作人员 */ public void setProductName(String productName,String user); /** * 获取订单订购的数量 * @return 订单订购的数量 */ public int getOrderNum(); /** * 设置订单订购的数量 * @param orderNum 订单订购的数量 * @param user 操作人员 */ public void setOrderNum(int orderNum,String user); /** * 获取创建订单的人员 * @return 创建订单的人员 */ public String getOrderUser(); /** * 设置创建订单的人员 * @param orderUser 创建订单的人员 * @param user 操作人员 */ public void setOrderUser(String orderUser,String user); } |
(2)订单对象
接下来定义订单对象,原本订单对象需要描述的属性很多,为了简单,只描述三个就好了,示例代码如下:
/** * 订单对象 */ public class Order implements OrderApi{ /** * 订单订购的产品名称 */ private String productName; /** * 订单订购的数量 */ private int orderNum; /** * 创建订单的人员 */ private String orderUser;
/** * 构造方法,传入构建需要的数据 * @param productName 订单订购的产品名称 * @param orderNum 订单订购的数量 * @param orderUser 创建订单的人员 */ public Order(String productName,int orderNum,String orderUser){ this.productName = productName; this.orderNum = orderNum; this.orderUser = orderUser; }
public String getProductName() { return productName; } public void setProductName(String productName,String user) { this.productName = productName; } public int getOrderNum() { return orderNum; } public void setOrderNum(int orderNum,String user) { this.orderNum = orderNum; } public String getOrderUser() { return orderUser; } public void setOrderUser(String orderUser,String user) { this.orderUser = orderUser; } } |
(3)订单对象的代理
创建好了订单对象,需要创建对它的代理对象了。既然订单代理就相当于一个订单,那么最自然的方式就是让订单代理跟订单对象实现一样的接口;要控制对订单setter方法的访问,那么就需要在代理的方法里面进行权限判断,有权限就调用订单对象的方法,没有权限就提示错误并返回。示例代码如下:
/** * 订单的代理对象 */ public class OrderProxy implements OrderApi{ /** * 持有被代理的具体的目标对象 */ private Order order=null; /** * 构造方法,传入被代理的具体的目标对象 * @param realSubject 被代理的具体的目标对象 */ public OrderProxy(Order realSubject){ this.order = realSubject; }
public void setProductName(String productName,String user) { //控制访问权限,只有创建订单的人员才能够修改 if(user!=null && user.equals(this.getOrderUser())){ order.setProductName(productName, user); }else{ System.out.println("对不起"+user +",您无权修改订单中的产品名称。"); } } public void setOrderNum(int orderNum,String user) { //控制访问权限,只有创建订单的人员才能够修改 if(user!=null && user.equals(this.getOrderUser())){ order.setOrderNum(orderNum, user); }else{ System.out.println("对不起"+user +",您无权修改订单中的订购数量。"); } } public void setOrderUser(String orderUser,String user) { //控制访问权限,只有创建订单的人员才能够修改 if(user!=null && user.equals(this.getOrderUser())){ order.setOrderUser(orderUser, user); }else{ System.out.println("对不起"+user +",您无权修改订单中的订购人。"); } }
public int getOrderNum() { return this.order.getOrderNum(); } public String getOrderUser() { return this.order.getOrderUser(); } public String getProductName() { return this.order.getProductName(); } public String toString(){ return "productName="+this.getProductName()+",orderNum=" +this.getOrderNum()+",orderUser="+this.getOrderUser(); } } |
(4)测试代码
一起来看看如何使用刚刚完成的订单代理,示例代码如下:
public class Client { public static void main(String[] args) { //张三先登录系统创建了一个订单 OrderApi order = new OrderProxy( new Order("设计模式",100,"张三")); //李四想要来修改,那就会报错 order.setOrderNum(123, "李四"); //输出order System.out.println("李四修改后订单记录没有变化:"+order); //张三修改就不会有问题 order.setOrderNum(123, "张三"); //再次输出order System.out.println("张三修改后,订单记录:"+order); } } |
运行结果如下:
对不起李四,您无权修改订单中的订购数量。 李四修改后订单记录没有变化: productName=设计模式,orderNum=100,orderUser=张三 张三修改后,订单记录:productName=设计模式,orderNum=123,orderUser=张三 |
从上面的运行结果就可以看出,在通过代理转调目标对象的时候,在代理对象里面,对访问的用户进行了权限判断,如果不满足要求,就不会转调目标对象的方法,从而保护了目标对象的方法,只让有权限的人操作。
11.3.3 Java中的代理
Java对代理模式提供了内建的支持,在java.lang.reflect包下面,提供了一个Proxy的类和一个InvocationHandler的接口。
通常把前面自己实现的代理模式,称为Java的静态代理。这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活,而使用Java内建的对代理模式支持的功能来实现则没有这个问题。
通常把使用Java内建的对代理模式支持的功能来实现的代理称为Java的动态代理。动态代理跟静态代理相比,明显的变化是:静态代理实现的时候,在Subject接口上定义很多的方法,代理类里面自然也要实现很多方法;而动态代理实现的时候,虽然Subject接口上定义了很多方法,但是动态代理类始终只有一个invoke方法。这样当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。
Java的动态代理目前只能代理接口,基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。具体的内部实现细节这里不去讨论。如果要实现类的代理,可以使用cglib(一个开源的Code Generation Library)。
还是来看看示例,那就修改上面保护代理的示例,看看如何使用Java的动态代理来实现同样的功能。
(1)订单接口的定义是完全一样的,就不去赘述了。
(2)订单对象的实现,只是添加了一个toString,以方便测试输出,这里也不去示例了。在前面的示例中,toString是实现在代理类里面了。
(3)直接看看代理类的实现,大致有如下变化:
- 要实现InvocationHandler接口
- 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好过后,返回被代理的目标对象的接口,以利于客户端的操作
- 需要实现invoke方法,在这个方法里面,去具体判断当前是在调用什么方法,需要如何处理。
示例代码如下:
/** * 使用Java中的动态代理 */ public class DynamicProxy implements InvocationHandler{ /** * 被代理的对象 */ private OrderApi order = null; /** * 获取绑定好代理和具体目标对象后的目标对象的接口 * @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")){ //如果不是创建人,那就不能修改 if(order.getOrderUser()!=null && order.getOrderUser().equals(args[1])){ //可以操作 return method.invoke(order, args); }else{ System.out.println("对不起,"+args[1] +",您无权修改本订单中的数据"); } }else{ //不是调用的setter方法就继续运行 return method.invoke(order, args); } return null; } } |
要看明白上面的实现,需要熟悉Java反射的知识,这里就不去展开了。
(4)看看现在的客户端如何使用这个动态代理,示例代码如下:
public class Client { public static void main(String[] args) { //张三先登录系统创建了一个订单 Order order = new Order("设计模式",100,"张三");
//创建一个动态代理 DynamicProxy dynamicProxy = new DynamicProxy(); //然后把订单和动态代理关联起来 OrderApi orderApi = dynamicProxy.getProxyInterface(order);
//以下就需要使用被代理过的接口来操作了 //李四想要来修改,那就会报错 orderApi.setOrderNum(123, "李四"); //输出order System.out.println("李四修改后订单记录没有变化:"+orderApi); //张三修改就不会有问题 orderApi.setOrderNum(123, "张三"); //再次输出order System.out.println("张三修改后,订单记录:"+orderApi); } } |
运行结果如下:
对不起,李四,您无权修改本订单中的数据 李四修改后订单记录没有变化: productName=设计模式,orderNum=100,orderUser=张三 张三修改后,订单记录:productName=设计模式,orderNum=123,orderUser=张三 |
运行的结果跟前面完全由自己实现的代理模式是一样的。
事实上,Java的动态代理还是实现AOP(面向方面编程)的一个重要手段,AOP的知识这里暂时不去讲述,大家先了解这一点就可以了。
11.3.4 代理模式的优缺点
代理模式在客户和被客户访问的对象之间,引入了一定程度的间接性,客户是直接使用代理,让代理来与被访问的对象进行交互。不同的代理类型,这种附加的间接性有不同的用途,也就是有不同的特点:
- 远程代理:隐藏了一个对象存在于不同的地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象,从客户的角度来讲,它只是在使用代理对象而已。
- 虚代理:可以根据需要来创建“大”对象,只有到必须创建对象的时候,虚代理才会创建对象,从而大大加快程序运行速度,并节省资源。通过虚代理可以对系统进行优化。
- 保护代理:可以在访问一个对象的前后,执行很多附加的操作,除了进行权限控制之外,还可以进行很多跟业务相关的处理,而不需要修改被代理的对象。也就是说,可以通过代理来给目标对象增加功能。
- 智能指引:跟保护代理类似,也是允许在访问一个对象的前后,执行很多附加的操作,这样一来就可以做很多额外的事情,比如:引用计数等。
11.3.5 思考代理模式
1:代理模式的本质
代理模式的本质:控制对象访问。
代理模式通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性,正是这个间接性,给了代理对象很多的活动空间,代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能,更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了,或是被替换掉了。
从实现上看,代理模式主要是使用对象的组合和委托,尤其是在静态代理的实现里面,会看得更清楚。但是也可以采用对象继承的方式来实现代理,这种实现方式在某些情况下,比使用对象组合还要来得简单。
举个例子来说明一下,改造11.3.2保护代理的例子来说明。
(1)首先就是去掉OrderApi,现在改成继承的方式实现代理,不再需要公共的接口了
(2)Order对象变化不大,只是去掉实现的OrderApi接口就好了,示例代码如下:
public class Order implements OrderApi { //其它的代码没有任何变化,就不去赘述了 } |
(3)再看看代理的实现,变化较多,大致有如下的变化:
- 不再实现OrderApi,而改成继承Order
- 不需要再持有目标对象了,因为这个时候父类就是被代理的对象
- 原来的构造方法去掉,重新实现一个传入父类需要的数据的构造方法
- 原来转调目标对象的方法,现在变成调用父类的方法了,用super关键字
- 除了几个被保护代理的setter方法外,不再需要getter方法了
示例代码如下:
/** * 订单的代理对象 */ 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 +",您无权修改订单中的订购人。"); } } public String toString(){ return "productName="+this.getProductName()+",orderNum=" +this.getOrderNum()+",orderUser="+this.getOrderUser(); } } |
(4)客户端的变化不大,主要是不再直接面向OrderApi接口,而是使用Order对象了,另外创建代理的构造方法也发生了变化,示例代码如下:
public class Client { public static void main(String[] args) { //张三先登录系统创建了一个订单 Order order = new OrderProxy("设计模式",100,"张三");
//李四想要来修改,那就会报错 order.setOrderNum(123, "李四"); //输出order System.out.println("李四修改后订单记录没有变化:"+order);
//张三修改就不会有问题 order.setOrderNum(123, "张三"); //再次输出order System.out.println("张三修改后,订单记录:"+order); } } |
去运行一下,测试看看,体会一下这种实现方式。
2:何时选用代理模式
建议在如下情况中,选用代理模式:
- 需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理
- 需要按照需要创建开销很大的对象的时候,可以使用虚代理
- 需要控制对原始对象的访问的时候,可以使用保护代理
- 需要在访问对象的时候执行一些附加操作的时候,可以使用智能指引代理
11.3.6 相关模式
l 代理模式和适配器模式
这两个模式有一定的相似性,但也有差异。
这两个模式有相似性,它们都为另一个对象提供间接性的访问,而且都是从自身以外的一个接口向这个对象转发请求。
但是从功能上,两个模式是不一样的。适配器模式主要用来解决接口之间不匹配的问题,它通常是为所适配的对象提供一个不同的接口;而代理模式会实现和目标对象相同的接口。
l 代理模式和装饰模式
这两个模式从实现上相似,但是功能上是不同的。
装饰模式的实现和保护代理的实现上是类似的,都是在转调其它对象的前后执行一定的功能。但是它们的目的和功能都是不同的。
装饰模式的目的是为了让你不生成子类就可以给对象添加职责,也就是为了动态的增加功能;而代理模式的主要目的是控制对对象的访问。
相关推荐
代理模式是一种设计模式,它在软件工程中扮演着重要的角色,允许我们为其他对象提供一个替代接口,以控制对原始对象的访问。这种模式的主要目的是为了增加灵活性、安全性或者在不修改原有对象的情况下,增强或扩展其...
代理模式是一种常用的设计模式,它在软件开发中扮演着重要的角色,特别是在iOS平台的应用程序设计中。代理模式的核心思想是为一个对象提供一个替身或代理,以控制对这个对象的访问。这种模式允许我们通过代理来间接...
代理模式是设计模式的一种,它提供了一种对目标对象进行增强或者控制访问的方式。在本实例中,我们将深入探讨Java中的代理模式及其应用。 代理模式的核心思想是为一个对象创建一个代理对象,这个代理对象在客户端和...
代理模式是设计模式中的一种结构型模式,它在对象交互中起到了中介的作用,允许通过代理对象来控制对原对象的访问。代理模式的核心思想是为一个对象提供一个替身,以便增加新的功能或者控制对原对象的访问。这种模式...
**设计模式之代理模式(Proxy Pattern)** 设计模式是软件工程中的一种最佳实践,它是在特定情境下解决常见问题的模板。代理模式是其中一种行为设计模式,它的核心思想是为一个对象提供一个替身或者代理,以控制对...
**设计模式实现——代理模式** 在软件工程中,设计模式是一种通用可重用的解决方案,它描述了在特定上下文中经常出现的问题以及该问题的解决方案。代理模式是设计模式的一种,它提供了一种对目标对象的间接访问方式...
3. **远程代理:**远程代理是代理模式的一个特殊应用,它使得客户端可以像操作本地对象一样操作远程对象,隐藏了远程调用的复杂性。Java RMI(Remote Method Invocation)技术就是一种实现远程代理的方式。 代理...
在这个“Java设计模式-代理模式例子”中,我们将深入探讨代理模式的概念、实现方式以及它在实际开发中的应用。 代理模式的核心思想是为一个对象提供一个替身,这个替身即代理对象,代理对象控制对原对象的访问。在...
代理模式是一种设计模式,属于结构型模式之一,其主要目的是为其他对象提供一个代理,以控制对该对象的访问。在实际应用中,代理模式能够帮助我们实现如下的功能: 1. 远程代理:代理对象可以代表一个位于远程系统...
3. **网络请求**:Android的Volley、Retrofit等网络库都利用了代理模式。例如,Retrofit可以通过拦截器(Interceptor)实现请求的预处理,如添加公共请求头、处理网络错误等。 4. **界面交互**:在界面组件(如按钮...
代理模式是一种常用的设计模式,它在软件开发中起到了中介或者代表的作用。代理模式的主要目的是为其他对象提供一种代理以控制对这个对象的访问。通过引入代理,我们可以增加新的功能,如缓存、日志记录、访问控制等...
SignalR提供了两种主要的工作模式:代理模式和非代理模式。这两种模式在实现上有所不同,各自具有优缺点,适用于不同的场景。 **1. 代理模式(Proxy Mode)** 在代理模式下,SignalR为每个Hub(服务端的业务逻辑...
3. 观察者模式扩展:在事件处理中,代理模式可以用来封装事件订阅和发布过程,使得事件订阅和处理更加灵活和易于管理。 4. UI解耦:代理模式可以用于界面逻辑和业务逻辑的分离,例如,一个点击事件的处理可以由代理...
### Java代理模式与Java动态代理详解 #### 一、代理模式概述 代理模式是一种软件设计模式,它在客户端和目标对象之间提供了一种间接层。这种模式的主要目的是控制客户端对目标对象的访问,并且可以在不修改原有...
代理模式(Proxy) 定义: 为其他对象提供一种代理以控制对这个对象的访问 结构: 由三部分组成 1.RealSubject(真实对象): 真正会调用到的对象 2.Proxy(代理对象): 代理真实对象的地方 3.Subject(共同点): 代理对象...
代理模式是一种常用的设计模式,它在软件开发中扮演着重要角色,允许我们通过一个代理类来控制对原对象的访问。在《设计模式:可复用面向对象软件的基础》(通常称为GoF设计模式)中,代理模式被定义为“为其他对象...
在Java编程中,代理模式是一种常用的面向对象设计模式,它允许我们为一个对象提供一个代理以控制对该对象的访问。代理模式通常用于增加额外的功能,如日志、权限检查等,或者为了创建虚拟代理以提高性能。以下是Java...
在IT行业中,代理模式是一种常见的设计模式,它允许我们在不修改原有对象的基础上,为对象添加新的功能或控制访问。在本示例中,我们将重点讨论如何在Java环境下使用代理模式来实现代理逻辑,特别是在CAS(Central ...