代理模式是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,其特征是代理类与委托类有同样的接口。
动机:
在软件设计中,使用代理模式的意图也很多,比如因为安全原因需要屏蔽客户端直接访问真实对象,或者在远程调用中需要使用代理类处理远程方法调用的技术细节 (如 RMI),也可能为了提升系统性能,通过控制来延迟对象的创建和实例化,直到真正需要使用该对象才进行创建和实例化。
由于一些对象创建和实例化需要占用大量系统资源,但我们并不能确定用户一定会调用该对象,所以通过延迟对象实例化来减缓系统资源的消耗。例如文档编辑器如word,我们可以在里面插入链接、图片等,但是并不是我们每次打开word时都有创建和实例化这些对象,特别是实例化图片对象很消耗资源,并不需要实例化所有图片。当我们在查看word时,只是看到其中的一部分,所以没有必要实例化所以资源,当我们看下一页时再实例化也不迟。
类型:结构类模式
类图:
图1 代理模式类图
代理模式角色:
1) 主题接口:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
2) 真实主题:真正实现业务逻辑的类;
3) 代理类:用来代理和封装真实主题;
4) Main:客户端,使用代理类和主题接口完成一些工作。
优点:
- 对客户端来说,隐藏了真实对象的细节及复杂性。
- 将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度。
- 在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用,也可以对目标对象调用之前进行其他操作。
- 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的性能与处理速度,可以快速响应并处理客户端请求。
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
- 安全代理可以控制对真实对象的使用权限。
缺点:
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
- 增加了系统的复杂度。
适用场景
1) 远程代理:也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个 Web 引用,引用一个 WebService,此时会在项目中声称一个 WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题。还有.NET的WCF的远程代理。
2) 虚拟代理:是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代理来替换了真实的图片,此时代理存储了真实图片的路径和尺寸。
3) 安全代理:用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
4) 指针引用:是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理。
5) 智能指引:当调用真实对象时,代理提供一些额外的操作。如将对象被操作的次数记录起来等。
6) 延迟加载,用代理模式实现延迟加载的一个经典应用就在 Hibernate 框架里面。当 Hibernate 加载实体 bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate 中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。
7) 缓冲代理:为某一个目标操作提供临时的存储空间,以便更多客户端共享此结果。
8) 防火墙代理:保护目标不让恶意用户接近。
9) 同步化代理:使几个用户能同时使用一个对象而没有冲突。
代理模式分类
1. 静态代理(静态定义代理类,自己静态定义的代理类)
1)优点:
可以做到在不修改目标对象的功能前提下,对目标功能扩展。
2)缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
3)静态代理模式角色:
- 抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口。
- 真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,真正的业务逻辑在此。
- 代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理。
代码实现:
JAVA
//接口 public interface IClient { void appeal();//谈判 } /** * 接口实现 * 目标对象 */ public class Client implements IClient { public void appeal() { System.out.println("委托人谈判"); } } /** * 代理对象,静态代理 */ public class ClientProxy implements IClient { private Client client; public ClientProxy(Client client) { this.client=client; } public void appeal() { System.out.println("代理谈判开始!"); client.appeal(); System.out.println("代理谈判结束!"); } } public class AppTest { public static void main(String[] args) { // TODO Auto-generated method stub //目标对象 Client target = new Client(); //代理对象,把目标对象传给代理对象,建立代理关系 ClientProxy proxy = new ClientProxy(target); proxy.appeal();//执行的是代理的方法 } }
输出结果:
C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace proxyDemo { // 客户端调用 class Program { static void Main(string[] args) { // 创建一个代理对象并发出请求 Person proxy = new Friend(); proxy.Appeal(); Console.Read(); } } // 抽象主题角色 public abstract class Person { public abstract void Appeal(); } //真实主题角色:委托人 public class Client : Person { public override void Appeal() { Console.WriteLine("委托人谈判中……"); } } // 代理角色:律师 public class Friend : Person { // 引用真实主题实例 Client client; public override void Appeal() { Console.WriteLine("律师代理谈判……"); if (client == null) { client = new Client(); } this.PreAppeal(); // 调用真实主题方法 client.Appeal(); this.PostAppeal(); } public void PreAppeal() { Console.WriteLine("谈判开始~"); } public void PostAppeal() { Console.WriteLine("谈判结束!"); } } }
输出结果:
2. 动态代理(通过程序动态生成代理类,该代理类不是自己定义的。而是由程序自动生成)
特点:
- 代理对象不需要实现接口
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- 动态代理也叫做:JDK代理,接口代理
基本原理:
通过扫描被代理类的所有 public 方法,并且自动生成一个从被代理类继承的类,然后在这个生成的类中 override 这些 public 方法。这里说的自动生成,并不是生成源代码,而是直接使用中间语言(Intermedial Language)直接在内存中生成。这样在速度上和源码编译而成的中间语言相近,可以利用 Reflection Emit API 来直接生成中间语言。
代码实现:
JAVA
public interface IClient { void appeal();//谈判 } /** * 接口实现 * 目标对象 */ public class Client implements IClient { public void appeal() { System.out.println("委托人谈判"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 处理器 */ public class ClientHandler implements InvocationHandler{ private IClient client;//真实角色 /** * 所有的流程控制都在invoke方法中 * proxy:代理类 * method:正在调用的方法 * args:方法的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object object = null; System.out.println("律师动态调用之前的处理....."); if (method.getName().equals("appeal")) { object = method.invoke(client, args);//激活调用的方法 } System.out.println("律师动态调用之后的处理....."); return object; } //通过构造器来初始化真实角色 public ClientHandler(IClient client) { super(); this.client = client; } } import java.lang.reflect.Proxy; public class MainTest { public static void main(String[] args) { // TODO Auto-generated method stub // 目标对象 IClient target = new Client(); // 【原始的类型 class cn.itcast.b_dynamic.UserDao】 System.out.println(target.getClass()); //处理器 ClientHandler handler = new ClientHandler(target); // 给目标对象,创建代理对象 IClient proxy = (IClient) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IClient.class}, handler); // class $Proxy0 内存中动态生成的代理对象 System.out.println(proxy.getClass()); // 执行方法 【代理对象】 proxy.appeal(); } }
输出结果: