代理模式

1 概念

别名

  • Surrogate

意图

为其他对象提供一种代理以控制对这个对象的访问。

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

结构

按照使用场景代理有以下四类:

  • 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
  • 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
  • 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
  • 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

505114124457.png

运行时一种可能的 Proxy 结构的对象图:

505464435187.png

参与者

Proxy

  • 保存一个引用使得代理可以访问实体。若 RealSubject 和 Subject
    的接口相同,Proxy 会引用 Subject。

  • 提供一个与 Subject 的接口相同的接口,这样 Proxy 就可以用来代替实体。

  • 控制对实体的存取,并可能负责创建和删除它。

  • 其他功能依赖于 Proxy 的类型:

    • 远程代理(Remote Proxy)负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。

    • 虚拟代理(Virtual Proxy)可以缓存实体的附加信息,以便延迟对它的访问。

    • 保护代理(Protection Proxy)检查调用者是否具有实现一个请求所必须的访问权限。

    • 智能代理(Smart Proxy)代替指针,计算引用计数

Subject

  • 定义 RealSubject 和 Proxy 的共用接口,这样就在任何使用 RealSubject
    的地方都可以使用 Proxy。

RealSubject

  • 定义 Proxy 所代表的实体。

适用性

下面是一些使用 Proxy 模式的常见情况:

  • 远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代表。

  • 虚拟代理(Virtual Proxy)根据需要创建开销很大的对象。

  • 保护代理(Protection Proxy)控制对原始对象的访问。

  • 智能代理(Smart Proxy)在访问对象时执行一些附件操作。

效果

  • Proxy 模式在访问对象时引入了一定程度的间接性。

  • Proxy 模式可以对用户隐藏 Copy-On-Write 优化方式。用 Proxy
    延迟对象拷贝过程,仅当这个对象被修改时才进行真正的拷贝,用以大幅度降低拷贝大实体的开销。

相关模式

  • Adapter 为它所适配的对象提供了一个不同的接口。Proxy
    提供了与它的实体相同的接口

  • Decorator 的实现与 Proxy 相似,但目的不一样。 Decorator为对象添加功能,Proxy 则控制对。实际体验上是完全一样的接口,都是在一个已经有的对象添加一些功能。以下是可能正确的一些区别:

    • 用法略有不同:装饰器模式关注于在一个对象上动态地添加方法,而代理模式关注于控制对对象的访问。换句话说,用代理模式,代理类可以对它的客户隐藏一个对象的具体信息。因此当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例;当使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰器的构造器。用法略有不同,装饰器和被装饰者都实现同一个接口。代理类和真是类都实现同一个接口。但是装饰器装饰一个已经存在的对象,需要从构造函数传入。代理类在内部创建一个完全独立的被代理对象,不需要在构造函数传入。
    • 关注的内容不同:装饰器模式和代理模式的使用场景不一样,比如IO流使用的是装饰者模式,可以层层增加功能。而代理模式则一般是用于增加特殊的功能,有些动态代理不支持多层嵌套。代理和装饰其实从另一个角度更容易去理解两个模式的区别:代理更多的是强调对对象的访问控制,比如说,访问A对象的查询功能时,访问B对象的更新功能时,访问C对象的删除功能时,都需要判断对象是否登陆,那么我需要将判断用户是否登陆的功能抽提出来,并对A对象、B对象和C对象进行代理,使访问它们时都需要去判断用户是否登陆,简单地说就是将某个控制访问权限应用到多个对象上;而装饰器更多的强调给对象加强功能,比如说要给只会唱歌的A对象添加跳舞功能,添加说唱功能等,简单地说就是将多个功能附加在一个对象上。

2 实现

以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。

1
2
3
public interface Image {
void showImage();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HighResolutionImage implements Image {

private URL imageURL;
private long startTime;
private int height;
private int width;

public int getHeight() {
return height;
}

public int getWidth() {
return width;
}

public HighResolutionImage(URL imageURL) {
this.imageURL = imageURL;
this.startTime = System.currentTimeMillis();
this.width = 600;
this.height = 600;
}

public boolean isLoad() {
// 模拟图片加载,延迟 3s 加载完成
long endTime = System.currentTimeMillis();
return endTime - startTime > 3000;
}

@Override
public void showImage() {
System.out.println("Real Image: " + imageURL);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ImageProxy implements Image {

private HighResolutionImage highResolutionImage;

public ImageProxy() {
highResolutionImage = new HighResolutionImage(url);
}

@Override
public void showImage() {
while (!highResolutionImage.isLoad()) {
try {
System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight());
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
highResolutionImage.showImage();
}
}
1
2
3
4
5
6
7
8
9
public class ImageViewer {

public static void main(String[] args) throws Exception {
String image = "http://image.jpg";
URL url = new URL(image);
ImageProxy imageProxy = new ImageProxy(highResolutionImage);
imageProxy.showImage();
}
}

对于装饰器可能需要如下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ImageDerector implements Image {

private HighResolutionImage highResolutionImage;

public ImageDerector(HighResolutionImage highResolutionImage) {
this.highResolutionImage = highResolutionImage;
}

@Override
public void showImage() {
while (!highResolutionImage.isLoad()) {
try {
System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight());
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
highResolutionImage.showImage();
}
}
public class ImageViewer {

public static void main(String[] args) throws Exception {
String image = "http://image.jpg";
URL url = new URL(image);
HighResolutionImage highResolutionImage = new HighResolutionImage(url);
ImageDerector imageDerector = new ImageDerector(highResolutionImage);
imageDerector.showImage();
}
}
`

JDK

  • java.lang.reflect.Proxy
  • RMI

3 代理模式和装饰器模式