代理模式

代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上也不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(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 给这种用法一个新名称:延迟加载。无论怎样,充分分析、了解这些开源框架的实现可以更好的感受经典设计模式的优势所在。

 

 

 

原文地址:https://www.cnblogs.com/jdbc/p/2629166.html