第15章 代理模式
15.1 代理模式概述
代理模式(Proxy Pattern):给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
15.2 代理模式结构与实现
15.2.1 代理模式结构
- Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
- Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象,在代理主题角色中提供了一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
- RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
15.2.2 代理模式实现
抽象主题类 Subject:
public abstract class Subject {
public abstract void request();
}
真实主题类 RealSubject:
public class RealSubject extends Subject {
public void request() {
//业务方法的具体实现代码
}
}
代理主题类 Proxy:
public class Proxy extends Subject {
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用
public void preRequest() {
...
}
public void request() {
preRequest();
realSubject.request(); //调用真实主题对象的方法
postRequest();
}
public void postRequest() {
}
}
实际开发中,代理类的实现比上述代码复杂更多,代理模式根据其目的和实现方法不同可以分为很多种类:
- 远程代理(Remote Proxy):为一个位于不同地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机上,也可以在另一台主机中。大使(Ambassador)
- 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 智能引用代理(Smart Reference Proxy):当一个对象被引用时提供一些额外的操作,例如将对象被调用的次数记录下来等。
15.3 代理模式应用实例
实例说明
某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:
(1)在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统。
(2)在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。
该软件公司的开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。
试使用代理模式设计并实现该收费商务信息查询系统。
实例类图
- 业务类
- AccessValidator
- Logger
- 抽象主题类
- Searcher
- 真实主题类
- RealSearcher
- 代理主题角色
- ProxySearcher
实例代码
AccessValidator:身份验证类(业务类),它提供 validate() 方法来实现身份验证。
package designpatterns.proxy;
public class AccessValidator {
//模拟实现登陆验证
public boolean validate(String userId) {
System.out.println("在数据库中验证用户'" + userId + "'是否为合法用户?");
if (userId.equalsIgnoreCase("杨过")) {
System.out.println("'" + userId + "'登录成功! ");
return true;
} else {
System.out.println("'" + userId + "'登录失败! ");
return false;
}
}
}
Logger:日志记录类(业务类),它提供 log() 方法来保存日志。
package designpatterns.proxy;
public class Logger {
//模拟实现日志记录
public void log(String userId) {
System.out.println("更新数据库,用户'" + userId + "'查询次数加1!");
}
}
Searcher:抽象查询类,充当抽象主题角色,它声明了 doSearch() 方法。
package designpatterns.proxy;
public interface Searcher {
public String doSearch(String userId, String keyword);
}
ProxySearcher:代理查询类,充当代理主题角色,它是查询代理,维持了对 RealSearcher 对象、AccessValidator 对象和 Logger 对象的引用。
package designpatterns.proxy;
public class ProxySearch implements Searcher {
private RealSearcher searcher = new RealSearcher();//维持一个对真实主题的引用
private AccessValidator validator;
private Logger logger;
@Override
public String doSearch(String userId, String keyword) {
//如果身份验证成功,则执行查询
if (this.validate(userId)) {
String result = searcher.doSearch(userId, keyword); //调用真实主题对象的查询方法
this.log(userId); //记录查询日志
return result; //返回查询结果
} else {
return null;
}
}
//创建访问验证对象并调用其validate()方法实现身份验证
public boolean validate(String userId) {
validator = new AccessValidator();
return validator.validate(userId);
}
//创建日志记录对象并调用其log()方法实现日志记录
public void log(String userId) {
logger = new Logger();
logger.log(userId);
}
}
RealSearcher:具体查询类,充当真实主题角色,它实现查询功能,提供 doSearch() 方法来查询信息。
package designpatterns.proxy;
public class RealSearcher implements Searcher {
@Override
//模拟查询商务信息
public String doSearch(String userId, String keyword) {
System.out.println("用户'" + userId + "'使用关键词'" + keyword + "'查询商务信息! ");
return "返回具体内容";
}
}
配置文件 config.xml,在配置文件中存储了代理主题类的类名。
XMLUtil:工具类。
<?xml version = "1.0"?>
<config>
<className>designpatterns.proxy.ProxySearch</className>
</config>
Client:客户端测试类。
package designpatterns.proxy;
public class Client {
public static void main(String[] args) {
Searcher searcher;
searcher = (Searcher) XMLUtil.getBean();
String result = searcher.doSearch("杨过", "玉女心经");
}
}
结果及分析
本实例是保护代理和智能引用代理的应用实例。
15.4 远程代理
远程代理(Remote Proxy):一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。
- 远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。
- 客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。
举例
RMI(Remote Method Invocation,远程方法调用):Java 语言中实现远程代理的机制,实现一个 Java 虚拟机中的对象调用另一个 Java 虚拟机中对象的方法。
- Stub(桩)对象;
- 远程对象在本地主机的代理独享。
- 桩对象和远程业务对象接口一致,对客户端而言操作远程对象和本地桩对象没有任何区别。
- Skeleton(骨架)对象。
- 远程主机中的与桩对象的通信对象。
RMI基本实现步骤:
- 客户端发起请求,将请求转交至RMI客户端的Stub类。
- Stub类将请求的接口、方法、参数等信息进行序列化。
- 将序列化后的流使用Socket传输至服务器端。
- 服务器端接收到流后将其转发至相应的Skeleton类。
- Skeleton类将请求信息反序列化后调用实际的业务处理类。
- 业务处理类处理完毕后将结果返回给Skeleton类。
- Skeleton类将结果序列化m,再次通过Socket将流传送给客户端的Stub。
- Stub在接收到流后进行反序列化,将反序列化后得到的Java Object对象返回给客户调用者。
15.5 虚拟代理
虚拟代理(Virtual Proxy):一种常用的代理模式,对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后虚拟代理将用户的请求转发给真实对象。