代理模式--导读
代理模式在我们生活中随处可见,当我们有事时我们但是我们又要跟别人交代一些事时,我们通常会叫别人帮我们转告,这也是代理。我们购物时找别人代购也是,在工作时我们在工作上需要别的部门帮忙时,我们常都时跟自己上级说,然后让上级跟自己想要的人说。这也是代理模式。当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口,这便是代理模式,根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。
代理模式--定义
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问
代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
代理模式--结构
下图是代理模式UML结构图
在代理模式中有如下三个角色:
Subject: 抽象角色。它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程
Proxy: 代理角色。它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
RealSubject: 真实角色。它代表着真实对象,是我们最终要引用的对象
代理模式--代码实现
下面我用歌手的代理模式来进行模式实现:
Sing.java抽象主题类声明了真实主题类和代理类的公共方法,它可以是接口、抽象类或具体类,客户端针对抽象主题类编程,一致性地对待真实主题和代理主题,代码如下:
package Proxy_Pattern; /** * 定义代理和真实调用对象都要实现的方法 * @author xyxy001 * */ public abstract class Sing { //歌手唱歌所需要的出场费 public int price; public void setPrice(int price){ this.price=price; } public abstract void sing(); }
Singer真实主题类继承了抽象主题类Sing.java,提供了业务方法的具体实现,代码如下:
package Proxy_Pattern; public class Singer extends Sing{ public String name; public Singer(int price,String name){ this.price=price; this.name=name; } public void sing() { System.out.println("歌手"+name+"在唱歌"); } }
SingerProxy.java代理类也是抽象主题类的子类,它维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法,在调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束,代码如下:
package Proxy_Pattern; /** * 歌手的代理,不仅要帮给歌手做好唱歌前的准备 * 当要唱歌时还要调用歌手的方法 * @author xyxy001 * */ public class SingerProxy extends Sing { private Singer singer; //歌手的出场次数 public static int count; public SingerProxy(){ //该代理所代理的歌手出场费一百万 this.singer=new Singer(1000000,"邓紫棋"); } public void sing() { this.promote(); if(this.talkSalary())//如果价格谈的来,则进行接下来的操作 { this.setLocation(); //叫歌手来唱歌 singer.sing(); this.manage(); //演唱次数加一 count++; }else{ System.out.println("你给的出场费根本不够"); } } //为歌手做宣传 public void promote(){ System.out.println("为歌手做宣传,提高知名度"); } //为歌手谈价钱 public boolean talkSalary(){ System.out.println("帮歌手谈好价钱"); if(price>singer.price) return true; else return false; } //查看歌手的出场次数 public int getCount(){ return count; } //帮歌手布置场地 public void setLocation(){ System.out.println("布置场地"); } //帮歌手处理演唱后的所有事宜 public void manage(){ System.out.println("帮歌手处理演唱后的事"); } }
XUMLUtil.java用于获取代理实例,当需要更换代理时仅仅需要更改配置文件config.xml并且添加一个新的代理类而不需要更具体代码更加符合开闭原则,具体代码如下:
package Proxy_Pattern; import java.io.File; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class XUMLUtil { public static Object getProxy() { try { DocumentBuilderFactory bFactory=DocumentBuilderFactory.newInstance(); DocumentBuilder db=bFactory.newDocumentBuilder(); Document doc=db.parse(new File("src//Proxy_Pattern//config.xml")); NodeList nodes=doc.getElementsByTagName("className"); Node node=nodes.item(0).getFirstChild(); String name=node.getNodeValue(); Class c=Class.forName(name); Object oc=c.newInstance(); return oc; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
配置文件,根据类名来进行获取实例化对象,具体信息如下:
<?xml version="1.0" encoding="UTF-8"?> <config> <className>Proxy_Pattern.SingerProxy</className> </config>
客户端代码用于描述真实案例,具体代码如下:
package Proxy_Pattern; /** * 模拟客户端请歌手唱歌 * @author xyxy001 * */ public class client { public static void main(String[] args) { //现在我公司打算用300万来请邓紫棋来我们公司进行演唱 //首先要找到邓紫棋的代理 Sing sing=(Sing)XUMLUtil.getProxy(); //告诉代理我用300万请邓紫棋唱歌 sing.setPrice(3000000); //代理执行请歌手唱歌的流程 sing.sing(); System.out.println("/*********************************/"); //现在同时有一个人也想请邓紫棋来唱歌,但是他只有50万 sing.setPrice(500000); sing.sing(); } }
下面是运行效果:
代理模式--拓展
在上述例子中,我们仅仅是使用简单的代理模式,但是在实际生活中代理模式的使用更加复杂,下面我来介绍一下几种生活中常见的代理:
一.远程代理:
远程代理(Remote Proxy)是一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。
如图中所示,客户端对象不能直接访问远程主机中的业务对象,只能采取间接访问的方式。远程业务对象在本地主机中有一个代理对象,该代理对象负责对远程业务对象的访问和网络通信,它对于客户端对象而言是透明的。客户端无须关心实现具体业务的是谁,只需要按照服务接口所定义的方式直接与本地主机中的代理对象交互即可。
远程模式的总结
- 拦截并控制方法调用(这也是代理模式最大的特点,最典型的,防火墙代理。。)
- 远程对象的存在对客户是透明的(客户完全把Stub代理对象当做远程对象了,虽然客户有点好奇为什么可能会出现异常。。)
- 远程代理隐藏了通信细节
二.虚拟代理
`虚拟代理(Virtual Proxy)也是一种常用的代理模式,对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象。
通常,在以下两种情况下可以考虑使用虚拟代理:
(1) 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真实对象。通常在实现时可以结合多线程技术,一个线程用于显示代理对象,其他线程用于加载真实对象。这种虚拟代理模式可以应用在程序启动的时候,由于创建代理对象在时间和处理复杂度上要少于创建真实对象,因此,在程序启动时,可以用代理对象代替真实对象初始化,大大加速了系统的启动时间。当需要使用真实对象时,再通过代理对象来引用,而此时真实对象可能已经成功加载完毕,可以缩短用户的等待时间。
(2) 当一个对象的加载十分耗费系统资源的时候,也非常适合使用虚拟代理。虚拟代理可以让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建,而在此之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象。为了节省内存,在第一次引用真实对象时再创建对象,并且该对象可被多次重用,在以后每次访问时需要检测所需对象是否已经被创建,因此在访问该对象时需要进行存在性检测,这需要消耗一定的系统时间,但是可以节省内存空间,这是一种用时间换取空间的做法。
无论是以上哪种情况,虚拟代理都是用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以在一定程度上提高系统的性能。
三.缓冲代理:
缓冲代理(Cache Proxy)也是一种较为常用的代理模式,它为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能。
在微软示例项目PetShop 4.0的业务逻辑层(Business Logic Layer, BLL)中定义了Product、Category、Item等类,它们封装了相关的业务方法,用于调用数据访问层(Data Access Layer, DAL)对象访问数据库,以获取相关数据。为了改进系统性能,PetShop 4.0为这些实现方法增加缓存机制,引入一个新的对象去控制原来的BLL业务逻辑对象,这些新的对象对应于代理模式中的代理对象。在引入代理模式后,实现了在缓存级别上对业务对象的封装,增强了对业务对象的控制,如果需要访问的数据在缓存中已经存在,则无须再重复执行获取数据的方法,直接返回存储在缓存中的数据即可。由于原有业务对象(真实对象)和新增代理对象暴露在外的方法是一致的,因而对于调用方即客户端而言,调用代理对象与真实对象并没有实质的区别。
四 保护代理:
保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
五 智能引用代理:
智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
代理模式--使用场景
(1) 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
(2) 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
(3) 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新 执行操作,只需直接从临时缓冲区获取操作结果即可。
(4) 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
(5) 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
代理模式--优缺点
优点
1、 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
2、 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的
缺点
1.由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2.’实现代理模式需要额外的工作,有些代理模式的实现非常复杂。