本文总结摘自刘伟老师的《设计模式》和程杰老师的《大话设计模式》
1.定义
代理模式:给某一个对象提供一个代理,并由代理对象控制对原对象的访问引用。代理模式英文叫做Proxy或Surrogate,它是一种对象结构模型。
2.为什么需要代理模式
某人要找对象,但是由于某些原因(如工作太忙)不能直接去找,于是委托一个中介机构去完成这一过程,如婚姻介绍所,在这里婚姻介绍所就是一个代理,与此相类似的还有房屋中介、职业中介,它们充当的都是一个代理的角色。所谓代理,就是一个人或者一个机构代表另一个或者另一个机构采取行动。在我们所开发的软件系统中有时候也存在这样的情况,如调用一个远程的方法,需要在本地设置一个代理,使得就像调用本地方法一样来使用远程的方法,这实际上也就是RMI、WebService等的实现原理。
在某些情况下,一个客户不想或不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
如在网页上查看一张图片,由于网速等原因图片不能立即显示,可以在图片传输过程中先把一些简单的用于描述图片的文字(或者小图片)传输到客户端,此时这些文字(或小图片)就成为了图片的代理。
再举一个例子,如果某台远程服务器提供了一个功能很强大的加密算法,而现在正在开发的系统又需要使用到该算法,由于该算法位于远程服务器端,封装该算法的对象位于远程服务器的内存中,本地内存中的对象无法直接访问,因此需要通过一个远程代理的机制来实现对远程对象的操作。
3.代理模式结构
3.1模式结构
模式结构如图3-1所示:
-
Subject类,定义了RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy
-
RealSubject类(被代理对象),定义Proxy所代表的真实实体
-
Proxy类,保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体
代理模式示意结构图比较简单,一般可以简化为如下图所示,但是在现实中要复杂很多。
主要代码如下:
public class Proxy implements Subject
{
private RealSubject realSubject = new RealSubject();
public void preRequest()
{…...}
public void request()
{
preRequest();
realSubject.request();
postRequest();
}
public void postRequest()
{……}
}
3.2例子---论坛权限控制代理
在一个论坛中已注册用户和游客的权限不同,已注册的用户拥有发帖、修改自己的注册信息、修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。 在本实例中我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
该实例类图如图3-2所示:
具体实现代码和相关解释如下:
(1)AbstractPermission(抽象权限类)
public interface AbstractPermission
{
public void modifyUserInfo();
public void viewNote();
public void publishNote();
public void modifyNote();
public void setLevel(int level);
}
AbstractPermission作为抽象权限类,在其中声明了真实主题角色(被代理对象)所提供的业务方法,它是被代理对象和代理对象的公共接口。
(2)RealPermission真实权限类(被代理类)
public class RealPermission implements AbstractPermission
{
public void modifyUserInfo()
{
System.out.println("修改用户信息!");
}
public void viewNote()
{ }
public void publishNote()
{
System.out.println("发布新帖!");
}
public void modifyNote()
{
System.out.println("修改发帖内容!");
}
public void setLevel(int level)
{ }
}
RealPermission是真实的被代理对象,它实现了在抽象主题角色中定义的方法,由于种种原因,客户端无法直接访问其中的方法。在RealPermission中包含了修改用户信息modifyUserInfo()、查看帖子viewNote()、发布新帖publishNote()、修改发帖内容modifyNote()、设置用户等级setLevel()等方法的实现。
(3)PermissionProxy(权限代理类)
public class PermissionProxy implements AbstractPermission
{
private RealPermission permission=new RealPermission();
private int level=0;
public void modifyUserInfo()
{
if(0==level)
{
System.out.println("对不起,你没有该权限!");
}
else if(1==level)
{
permission.modifyUserInfo();
}
}
public void viewNote()
{
System.out.println("查看帖子!");
}
public void publishNote()
{
if(0==level)
{
System.out.println("对不起,你没有该权限!");
}
else if(1==level)
{
permission.publishNote();
}
}
public void modifyNote()
{
if(0==level)
{
System.out.println("对不起,你没有该权限!");
}
else if(1==level)
{
permission.modifyNote();
}
}
public void setLevel(int level)
{
this.level=level;
}
}
PermissionProxy是代理对象,它也实现了抽象主题角色接口,同时在PermissionProxy中定义了一个RealPermission对象,用于调用在RealPermission中定义的真实业务方法,在PermissionProxy类的modifyUserInfo()、publishNote()、modifyNote()方法中将对用户的权限进行判断,如果具有相应权限则调用RealPermission中定义的方法,否则拒绝调用。通过引用PermissionProxy类来对系统的使用权限进行控制,这就是保护代理的用途。
(4)客户端代码
public class Client
{
public static void main(String args[])
{
AbstractPermission permission;
permission= new PermissionProxy();
permission.modifyUserInfo();
permission.viewNote();
permission.publishNote();
permission.modifyNote();
System.out.println("----------------------------");
permission.setLevel(1);
permission.modifyUserInfo();
permission.viewNote();
permission.publishNote();
permission.modifyNote();
}
}
运行结果:
4.何时使用代理模式
根据代理模式的使用目的,常见的代理模式有以下几种类型:
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
- 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
- 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 防火墙(Firewall)代理:保护目标不让恶意用户接近。
- 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
- 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
5.模式应用
-
EJB、Web Service等分布式技术都是代理模式的应用。在EJB中使用了RMI机制,远程服务器中的企业级Bean在本地有一个桩代理,客户端通过桩来调用远程对象中定义的方法,而无须直接与远程对象交互。在EJB的使用中需要提供一个公共的接口,客户端针对该接口进行编程,无须知道桩以及远程EJB的实现细节。
-
Spring 框架中的AOP技术也是代理模式的应用,在Spring AOP中应用了动态代理(Dynamic Proxy)技术。
6.扩展
6.1几种常用的代理模式
(1)图片代理
一个很常见的代理模式的应用实例就是对大图浏览的控制。 用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
(2)远程代理
远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的远程业务对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。(webService)
(3)虚拟代理
当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了。虚拟代理模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。 在应用程序启动的时候,可以用代理对象代替真实对象初始化,节省了内存的占用,并大大加速了系统的启动时间。
6.2 动态代理
动态代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。 在传统的代理模式中,客户端通过Proxy调用RealSubject类的request()方法,同时还在代理类中封装了其他方法(如preRequest()和postRequest()),可以处理一些其他问题。 如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数,此外,如何在事先不知道真实主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问题。