zoukankan      html  css  js  c++  java
  • 代理模式

    代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上也不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。

    在这种设计方式下,系统会为某个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个 Java 对象代表另一个 Java 对象来采取行动。在某些情况下,客户端代码不想或不能够直接调用被调用者,代理对象可以在客户和目标对象之间起到中介的作用。

    对客户端而言,它不能分辨出代理对象与真实对象的区别,它也无须分辨代理对象和真实对象的区别。客户端代码并不知道真正的被代理对象,客户端代码面向接口编程,它仅仅持有一个被代理对象的接口。

    总而言之,只要客户端代码不能或不想直接访问被调用对象——这种情况有很多原因,比如需要创建一个系统开销很大的对象,或者被调用对象在远程主机上,或者目标对象的功能还不足以满足需求……,而是额外创建一个代理对象返回给客户端使用,那么这种设计方式就是代理模式。

    下面示范一个简单的代理模式,程序首先提供了一个 Image 接口,代表大图片对象所实现的接口,该接口代码如下:


    清单 3. Image.java

    1  public interface Image 
    2  { 
    3  void show(); 
    4  } 

    该接口提供了一个实现类,该实现类模拟了一个大图片对象,该实现类的构造器使用 Thread.sleep() 方法来暂停 3s。下面是该 BigImage 的程序代码。

    清单 4. BigImage.java

     1 // 使用该 BigImage 模拟一个很大图片
     2  public class BigImage implements Image 
     3  { 
     4  public BigImage() 
     5  { 
     6  try 
     7  { 
     8  // 程序暂停 3s 模式模拟系统开销 
     9                  Thread.sleep(3000); 
    10  System.out.println("图片装载成功 ..."); 
    11  } 
    12  catch (InterruptedException ex) 
    13  { 
    14  ex.printStackTrace(); 
    15  } 
    16  } 
    17  // 实现 Image 里的 show() 方法
    18  public void show() 
    19  { 
    20  System.out.println("绘制实际的大图片"); 
    21  } 
    22  } 

    上面的程序代码暂停了 3s,这表明创建一个 BigImage 对象需要 3s 的时间开销——程序使用这种延迟来模拟装载此图片所导致的系统开销。如果不采用代理模式,当程序中创建 BigImage 时,系统将会产生 3s 的延迟。为了避免这种延迟,程序为 BigImage 对象提供一个代理对象,BigImage 类的代理类如下所示。


    清单 5. ImageProxy.java

     1 public class ImageProxy implements Image 
     2  { 
     3  // 组合一个 image 实例,作为被代理的对象
     4  private Image image; 
     5  // 使用抽象实体来初始化代理对象
     6  public ImageProxy(Image image) 
     7  { 
     8  this.image = image; 
     9  } 
    10  /** 
    11  * 重写 Image 接口的 show() 方法
    12  * 该方法用于控制对被代理对象的访问,
    13  * 并根据需要负责创建和删除被代理对象
    14  */ 
    15  public void show() 
    16  { 
    17  // 只有当真正需要调用 image 的 show 方法时才创建被代理对象
    18  if (image == null) 
    19  { 
    20   image = new BigImage(); 
    21   } 
    22  image.show(); 
    23  } 
    24  } 

    上面的 ImageProxy 代理类实现了与 BigImage 相同的 show() 方法,这使得客户端代码获取到该代理对象之后,可以将该代理对象当成 BigImage 来使用。

    在 ImageProxy 类的 show() 方法中增加了控制逻辑,这段控制逻辑用于控制当系统真正调用 image 的 show() 时,才会真正创建被代理的 BigImage 对象。下面程序需要使用 BigImage 对象,但程序并不是直接返回 BigImage 实例,而是先返回 BigImage 的代理对象,如下面程序所示。


    清单 6. BigImageTest.java

     1 public class BigImageTest 
     2  { 
     3  public static void main(String[] args) 
     4  { 
     5  long start = System.currentTimeMillis(); 
     6  // 程序返回一个 Image 对象,该对象只是 BigImage 的代理对象
     7  Image image = new ImageProxy(null); 
     8  System.out.println("系统得到 Image 对象的时间开销 :" + 
     9  (System.currentTimeMillis() - start)); 
    10  // 只有当实际调用 image 代理的 show() 方法时,程序才会真正创建被代理对象。
    11  image.show(); 
    12  } 
    13  } 

    上面程序初始化 image 非常快,因为程序并未真正创建 BigImage 对象,只是得到了 ImageProxy 代理对象——直到程序调用 image.show() 方法时,程序需要真正调用 BigImage 对象的 show() 方法,程序此时才真正创建 BigImage 对象。运行上面程序,看到如图 6 所示的结果。


    图 6. 使用代理模式提高性能

     

    看到如图 6 所示的运行结果,读者应该能认同:使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问:程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊,系统开销并未真正减少啊?只是这种系统开销延迟了而已啊?

    我们可以从如下两个角度来回答这个问题:

    • 把创建 BigImage 推迟到真正需要它时才创建,这样能保证前面程序运行的流畅性,而且能减少 BigImage 在内存中的存活时间,从宏观上节省了系统的内存开销。
    • 有些情况下,也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下,使用代理模式可以显著地提高系统运行性能。

    与此完全类似的是,Hibernate 也是通过代理模式来“推迟”加载关联实体的时间,如果程序并不需要访问关联实体,那程序就不会去抓取关联实体了,这样既可以节省系统的内存开销,也可以缩短 Hibernate 加载实体的时间。

     

    小结

    Hibernate 的延迟加载(lazy load)本质上就是代理模式的应用,我们在过去的岁月里就经常通过代理模式来降低系统的内存开销、提升应用的运行性能。Hibernate 充分利用了代理模式的这种优势,并结合了 Javassist 或 CGLIB 来动态地生成代理对象,这更加增加了代理模式的灵活性,Hibernate 给这种用法一个新名称:延迟加载。无论怎样,充分分析、了解这些开源框架的实现可以更好的感受经典设计模式的优势所在。

     

     

     

  • 相关阅读:
    方法引用(method reference)
    函数式接口
    Lambda 表达式
    LinkedList 源码分析
    ArrayList 源码分析
    Junit 学习笔记
    Idea 使用 Junit4 进行单元测试
    Java 定时器
    【干货】Mysql的"事件探查器"-之Mysql-Proxy代理实战一(安装部署与实战sql拦截与性能监控)
    python-flask框架web服务接口开发实例
  • 原文地址:https://www.cnblogs.com/jdbc/p/2629166.html
Copyright © 2011-2022 走看看