JAVA设计模式(09):结构型-代理模式(Proxy)

时间:2024-01-15 09:33:50

代理模式是经常使用的结构型设计模式之中的一个,当无法直接訪问某个对象或訪问某个对象存在困难时能够通过一个代理对象来间接訪问,为了保证client使用的透明性,所訪问的真实对象与代理对象须要实现同样的接口。依据代理模式的使用目的不同,代理模式又能够分为多种类型。比如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。

1 代理模式概述

近年来。代购已逐步成为电子商务的一个重要分支。何谓代购。简单来说就是找人帮忙购买所须要的商品,当然你可能须要向实施代购的人支付一定的费用。代购通常分为两种类型:一种是由于在当地买不到某件商品。又或者是由于当地这件商品的价格比其它地区的贵,因此托人在其它地区甚至国外购买该商品,然后通过快递发货或者直接携带回来。另一种代购,由于消费者对想要购买的商品相关信息的缺乏。自已无法确定事实上际价值而又不想被商家宰。仅仅好托付中介机构帮其讲价或为其代买。代购站点为此应运而生,它为消费者提供在线的代购服务。假设看中某国外购物站点上的商品,能够登录代购站点填写代购单并付款,代购站点会帮助进行购买然后通过快递公司将商品发送给消费者。商品代购过程如图1所看到的:

JAVA设计模式(09):结构型-代理模式(Proxy)

图1 商品代购示意图

在软件开发中,也有一种设计模式能够提供与代购站点类似的功能。因为某些原因,client不想或不能直接訪问一个对象,此时能够通过一个称之为“代理”的第三者来实现间接訪问。该方案相应的设计模式被称为代理模式。

代理模式是一种应用非常广泛的结构型设计模式,并且变化形式非常多,常见的代理形式包含远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等,后面将学习这些不同的代理形式。

代理模式定义例如以下:

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的訪问。

Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it.

代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在client对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户须要的额外的新服务。

2 代理模式结构与实现

2.1 模式结构

代理模式的结构比較简单。其核心是代理类。为了让client可以一致性地对待真实对象和代理对象。在代理模式中引入了抽象层,代理模式结构如图9-2所看到的:

JAVA设计模式(09):结构型-代理模式(Proxy)

图2 代理模式结构图

由图2可知,代理模式包括例如以下三个角色:

       (1) Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在不论什么使用真实主题的地方都能够使用代理主题,client通常须要针对抽象主题角色进行编程。

       (2) Proxy(代理主题角色):它包括了对真实主题的引用,从而能够在不论什么时候操作真实主题对象。在代理主题角色中提供一个与真实主题角色同样的接口。以便在不论什么时候都能够替代真实主题;代理主题角色还能够控制对真实主题的使用,负责在须要的时候创建和删除真实主题对象。并对真实主题对象的使用加以约束。通常,在代理主题角色中,client在调用所引用的真实主题操作之前或之后还须要运行其它操作,而不不过单纯调用真实主题对象中的操作。

       (3) RealSubject(真实主题角色):它定义了代理角色所代表的真实对象。在真实主题角色中实现了真实的业务操作,client能够通过代理主题角色间接调用真实主题角色中定义的操作。

2.2 模式实现

代理模式的结构图比較简单,可是在真实的使用和实现过程中要复杂非常多,特别是代理类的设计和实现。

抽象主题类声明了真实主题类和代理类的公共方法。它能够是接口、抽象类或详细类,client针对抽象主题类编程。一致性地对待真实主题和代理主题。典型的抽象主题类代码例如以下:

public abstract class Subject  {
public abstract void Request();
}

真实主题类继承了抽象主题类,提供了业务方法的详细实现。其典型代码例如以下:

public class RealSubject extends Subject{
@Override
public void Request() {
//业务方法详细实现代码
}
}

代理类也是抽象主题类的子类,它维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法,在调用时能够在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束,最简单的代理类实现代码例如以下:

public class Proxy extends Subject{

    private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用  

    public void preRequest(){
//…...
} @Override
public void request(){
preRequest();
realSubject.Request(); //调用真实主题对象的方法
postRequest();
} public void postRequest(){
//……
}
}

在实际开发过程中。代理类的实现比上述代码要复杂非常多,代理模式依据其目的和实现方式不同可分为非常多种类,当中经常使用的几种代理模式简要说明例如以下:

 (1) 远程代理(Remote Proxy)为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间能够是在同一台主机中,也但是在还有一台主机中,远程代理又称为大使(Ambassador)。

 (2) 虚拟代理(Virtual Proxy):假设须要创建一个资源消耗较大的对象。先创建一个消耗相对较小的对象来表示,真实对象仅仅在须要时才会被真正创建。

 (3) 保护代理(Protect Proxy)控制对一个对象的訪问。能够给不同的用户提供不同级别的使用权限。

 (4) 缓冲代理(Cache Proxy)为某一个目标操作的结果提供暂时的存储空间,以便多个client能够共享这些结果。

   (5) 智能引用代理(Smart Reference Proxy)当一个对象被引用时。提供一些额外的操作,比如将对象被调用的次数记录下来等。

在这些经常使用的代理模式中。有些代理类的设计很复杂,比如远程代理类,它封装了底层网络通信和对远程对象的调用,事实上现较为复杂。

3 代理模式应用实例

以下通过一个应用实例来进一步学习和理解代理模式。

       1. 实例说明

       某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求例如以下:

       (1) 在进行商务信息查询之前用户须要通过身份验证,仅仅有合法用户才可以使用该查询系统。

       (2) 在进行商务信息查询时系统须要记录查询日志,以便依据查询次数收取查询费用。

       该软件公司开发者已完毕了商务信息查询模块的开发任务,现希望可以以一种松耦合的方式向原有系统添加身份验证和日志记录功能。client代码可以无差别地对待原始的商务信息查询模块和添加新功能之后的商务信息查询模块,并且可能在将来还要在该信息查询模块中添加一些新的功能。

       试使用代理模式设计并实现该收费商务信息查询系统。

       2. 实例分析及类图

通过分析。能够採用一种间接訪问的方式来实现该商务信息查询系统的设计。在client对象和信息查询对象之间添加一个代理对象,让代理对象来实现身份验证和日志记录等功能,而无须直接对原有的商务信息查询对象进行改动,如图3所看到的:

JAVA设计模式(09):结构型-代理模式(Proxy)

图3 商务信息查询系统设计方案示意图

在图3中,client对象通过代理对象间接訪问具有商务信息查询功能的真实对象。在代理对象中除了调用真实对象的商务信息查询功能外。还添加了身份验证和日志记录等功能。使用代理模式设计该商务信息查询系统,结构图如图4所看到的。

JAVA设计模式(09):结构型-代理模式(Proxy)

图4 商务信息查询系统结构图

在图4中,业务类AccessValidator用于验证用户身份,业务类Logger用于记录用户查询日志,Searcher充当抽象主题角色。RealSearcher充当真实主题角色,ProxySearcher充当代理主题角色。

       3. 实例代码

(1) AccessValidator:身份验证类。业务类。它提供方法validate()来实现身份验证。

public class AccessValidator {
// 模拟实现登录验证
public boolean validate(String userId) {
System.out.println(String.format("在数据库中验证用户'%s'是否是合法用户?", userId));
if (userId.equals("杨过")) {
System.out.println(userId + "登录成功!");
return true;
} else {
System.out.println(userId + "登录失败!");
return false;
}
}
}

(2) Logger:日志记录类。业务类,它提供方法log()来保存日志。

public class Logger {
//模拟实现日志记录
public void log(String userId) {
System.out.println(String.format("更新数据库,用户'%s'查询次数加1", userId));
}
}

(3) Searcher:抽象查询类,充当抽象主题角色。它声明了doSearch()方法。

public interface Searcher {
public String doSearch(String userId, String keyword);
}

(4) RealSearcher:详细查询类,充当真实主题角色。它实现查询功能,提供方法doSearch()来查询信息。

public class RealSearcher implements Searcher{
@Override
public String doSearch(String userId, String keyword) {
System.out.println(String.format("用户%s使用关键词%s查询商务信息。", userId,keyword));
return "返回详细内容";
}
}

(5) ProxySearcher:代理查询类,充当代理主题角色。它是查询代理,维持了对RealSearcher对象、AccessValidator对象和Logger对象的引用。

public class ProxySearcher 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);
} }

(6) 配置文件config.xml,在配置文件里存储了代理主题类类名。

<?

xml version="1.0" encoding="UTF-8"?>
<config>
<className>com.somnus.designPatterns.proxy.ProxySearcher</className>
</config>

(7) Program:client測试类

public class Client {

	public static void main(String[] args) throws Exception{
Searcher searcher = (Searcher) XMLUtil.getBean();
searcher.doSearch("杨过", "玉女心经");
} }

       4. 结果及分析

编译并执行程序,输出结果例如以下:

在数据库中验证用户'杨过'是否是合法用户?

'杨过'登录成功。

用户'杨过'使用关键词'玉女心经'查询商务信息。

更新数据库。用户'杨过'查询次数加1。

本实例是保护代理智能引用代理的应用实例。在代理类ProxySearcher中实现对真实主题类的权限控制和引用计数。假设须要在訪问真实主题时添加新的訪问控制机制和新功能。仅仅需添加一个新的代理类,再改动配置文件,在client代码中使用新增代理类就可以。源码无须改动。符合开闭原则。

4 远程代理

  远程代理(Remote Proxy)是一种经常使用的代理模式,它使得client程序能够訪问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,能够高速响应并处理client的请求。远程代理能够将网络的细节隐藏起来。使得client不必考虑网络的存在。client全然能够觉得被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。

远程代理示意图如图5所看到的,client对象不能直接訪问远程主机中的业务对象,仅仅能採取间接訪问的方式。

远程业务对象在本地主机中有一个代理对象,该代理对象负责对远程业务对象的訪问和网络通信,它对于client对象而言是透明的。client无须关心实现详细业务的是谁,仅仅须要依照服务接口所定义的方式直接与本地主机中的代理对象交互就可以。

JAVA设计模式(09):结构型-代理模式(Proxy)

图5 远程代理示意图

在基于.NET平台的分布式技术,比如DCOM(Distribute Component Object Model,分布式组件对象模型)、Web
Service中。都应用了远程代理模式,大家能够查阅相关文档进行扩展学习。

5 虚拟代理

 虚拟代理(Virtual Proxy)也是一种经常使用的代理模式,对于一些占用系统资源较多或者载入时间较长的对象,能够给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后。虚拟代理将用户的请求转发给真实对象。

通常。在下面两种情况下能够考虑使用虚拟代理:

(1) 因为对象本身的复杂性或者网络等原因导致一个对象须要较长的载入时间,此时能够用一个载入时间相对较短的代理对象来代表真实对象。

通常在实现时能够结合多线程技术,一个线程用于显示代理对象,其它线程用于载入真实对象。这样的虚拟代理模式能够应用在程序启动的时候,因为创建代理对象在时间和处理复杂度上要少于创建真实对象,因此,在程序启动时,能够用代理对象取代真实对象初始化,大大加速了系统的启动时间。

当须要使用真实对象时,再通过代理对象来引用,而此时真实对象可能已经成功载入完成。能够缩短用户的等待时间。

(2) 当一个对象的载入十分耗费系统资源的时候。也很适合使用虚拟代理。

虚拟代理能够让那些占用大量内存或处理起来很复杂的对象推迟到使用它们的时候才创建。而在此之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象。

为了节省内存。在第一次引用真实对象时再创建对象。而且该对象可被多次重用,在以后每次訪问时须要检測所需对象是否已经被创建,因此在訪问该对象时须要进行存在性检測,这须要消耗一定的系统时间。可是能够节省内存空间,这是一种用时间换取空间的做法。

不管是以上哪种情况。虚拟代理都是用一个“虚假”的代理对象来代表真实对象。通过代理对象来间接引用真实对象,能够在一定程度上提高系统的性能。

6 虚拟代理

缓冲代理(Cache Proxy)也是一种较为经常使用的代理模式,它为某一个操作的结果提供暂时的缓存存储空间,以便在兴许使用中可以共享这些结果,从而可以避免某些方法的反复运行,优化系统性能。

在微软演示样例项目PetShop 4.0的业务逻辑层(Business Logic Layer, BLL)中定义了Product、Category、Item等类。它们封装了相关的业务方法,用于调用数据訪问层(Data
Access Layer, DAL)对象訪问数据库,以获取相关数据。

为了改进系统性能,PetShop 4.0为这些实现方法添加缓存机制,引入一个新的对象去控制原来的BLL业务逻辑对象,这些新的对象相应于代理模式中的代理对象。在引入代理模式后,实现了在缓存级别上对业务对象的封装,增强了对业务对象的控制。假设须要訪问的数据在缓存中已经存在,则无须再反复运行获取数据的方法,直接返回存储在缓存中的数据就可以。因为原有业务对象(真实对象)和新增代理对象暴露在外的方法是一致的,因而对于调用方即client而言。调用代理对象与真实对象并没有实质的差别。

这些新引入的代理类包含ProductDataProxy、CategoryDataProxy和ItemDataProxy等。以下以PetShop.BLL.Product业务对象为例进行说明,PetShop
4.0为其建立了代理对象ProductDataProxy,并在ProductDataProxy的GetProductsByCategory()方法中调用了业务逻辑层Product类的GetProductsByCategory()方法,同一时候添加了缓存机制。

如图9-6所看到的:

JAVA设计模式(09):结构型-代理模式(Proxy)

图9-6 PetShop4.0缓存代理示意图

在ProductDataProxy类中存在例如以下代码片段:

[csharp] view
plain
copy
  1. public static class ProductDataProxy
  2. {
  3. private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings ["ProductCacheDuration"]);
  4. private static readonly bool enableCaching = bool.Parse(ConfigurationManager. AppSettings["EnableCaching"]);
  5. public static IList GetProductsByCategory(string category)
  6. {
  7. Product product = new Product();
  8. //假设缓存被禁用,则直接通过product对象来获取数据
  9. if (!enableCaching)
  10. {
  11. return product.GetProductsByCategory(category);
  12. }
  13. string key = "product_by_category_" + category;
  14. //从缓存中获取数据
  15. IList data = (IList )HttpRuntime.Cache[key];
  16. //假设缓存中没有数据则运行例如以下代码
  17. if (data == null)
  18. {
  19. data = product.GetProductsByCategory(category);
  20. //通过工厂创建AggregateCacheDependency对象
  21. AggregateCacheDependency cd = DependencyFacade.GetProductDependency ();
  22. //将数据存储在缓存中,并加入必要的AggregateCacheDependency对象
  23. HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(product Timeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
  24. }
  25. return data;
  26. }
  27. ……
  28. }

在上述代码中,AggregateCacheDependency是从.NET Framework 2.0開始新增的一个类,它负责监视依赖项对象的集合。

当这个集合中的随意一个依赖项对象发生改变时,该依赖项对象相应的缓存对象都将被自己主动移除。在此不正确AggregateCacheDependency进行具体说明,大家能够查阅相关文档进行扩展学习。

与业务逻辑层Product对象的GetProductsByCategory()方法相比,上述代码添加了缓存机制。

当缓存内不存在相关数据项时,则直接调用业务逻辑层Product的GetProductsByCategory()方法来获取数据,并将其与相应的AggregateCacheDependency对象一起存储在缓存中。在ProductDataProxy类的每个业务方法中都实例化了Product类。再调用Product类的相应方法,因此ProductDataProxy与Product之间属于依赖关系,这是标准代理模式的一种变形,能够依照标准代理模式对其进行改进,包含引入高层的抽象接口。

7 代理模式效果与适用场景

代理模式是经常使用的结构型设计模式之中的一个。它为对象的间接訪问提供了一个解决方式,能够对对象的訪问进行控制。代理模式类型较多,当中远程代理、虚拟代理、保护代理等在软件开发中应用很广泛。

7.1 模式长处

代理模式的共同长处例如以下:

(1) 可以协调调用者和被调用者。在一定程度上减少了系统的耦合度。

(2) client能够针对抽象主题角色进行编程。添加和更换代理类无须改动源码。符合开闭原则,系统具有较好的灵活性和可扩展性。

此外,不同类型的代理模式也具有独特的长处,比如:

(1) 远程代理为位于两个不同地址空间对象的訪问提供了一种实现机制,能够将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的总体执行效率。

(2) 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象。能够在一定程度上节省系统的执行开销。

(3) 缓冲代理为某一个操作的结果提供暂时的缓存存储空间。以便在兴许使用中可以共享这些结果。优化系统性能,缩短运行时间。

(4) 保护代理能够控制对一个对象的訪问权限。为不同用户提供不同级别的使用权限。

7.2 模式缺点

代理模式的主要缺点例如以下:

(1) 因为在client和真实主题之间添加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,比如保护代理。

(2) 实现代理模式须要额外的工作,并且有些代理模式的实现过程较为复杂。比如远程代理。

7.3 模式适用场景

代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合:

       (1) 当client对象须要訪问远程主机中的对象时能够使用远程代理。

       (2) 当须要用一个消耗资源较少的对象来代表一个消耗资源较多的对象。从而减少系统开销、缩短执行时间时能够使用虚拟代理,比如一个对象须要非常长时间才干完毕载入时。

       (3) 当须要为某一个被频繁訪问的操作结果提供一个暂时存储空间。以供多个client共享訪问这些结果时能够使用缓冲代理。

通过使用缓冲代理。系统无须在client每一次訪问时都又一次运行操作。仅仅需直接从暂时缓冲区获取操作结果就可以。

       (4) 当须要控制对一个对象的訪问。为不同用户提供不同级别的訪问权限时能够使用保护代理。

       (5) 当须要为一个对象的訪问(引用)提供一些额外的操作时能够使用智能引用代理。

习题

1. Windows操作系统中的应用程序快捷方式是(    )模式的应用实例。

A. 代理 (Proxy)            B. 组合 (Composite)

C. 装饰 (Decorator)         D. 外观 (Facade)

2. 毕业生通过职业介绍所找工作。请问该过程蕴含了哪种设计模式,绘制对应的类图。

3. 在某应用软件中须要记录业务方法的调用日志,在不改动现有业务类的基础上为每个类提供一个日志记录代理类,在代理类中输出日志,如在业务方法Method()调用之前输出“方法Method()被调用。调用时间为2012-11-5
10:10:10”,调用之后假设没有抛异常则输出“方法Method()调用成功”,否则输出“方法Method()调用失败”。

在代理类中调用真实业务类的业务方法。使用代理模式设计该日志记录模块的结构,绘制类图并使用C#语言编程模拟实现。

4. 某软件公司欲开发一款基于C/S的网络图片查看器。详细功能描写叙述例如以下:用户仅仅需在图片查看器中输入网页URL。程序将自己主动将该网页全部图片下载到本地,考虑到有些网页图片比較多,并且某些图片文件比較大,因此将先以图标的方式显示图片。不同类型的图片使用不同的图标,并且在图标以下标注该图片的文件名称。用户单击图标后可查看真正的图片,界面效果如图7所看到的。试使用虚拟代理模式设计并实现该图片查看器。(注:能够结合多线程机制。使用一个线程显示小图标,同一时候启动还有一个线程在后台载入原图。

JAVA设计模式(09):结构型-代理模式(Proxy)

图7 图片查看器界面效果图

【友情提示:建议大家有时间的话把这些练习都做一做,有问题欢迎讨论!】

【作者:刘伟(Sunny)  http://blog.csdn.net/lovelion