设计模式学习之代理模式

什么是代理模式

代理模式(Proxy Design Pattern)为另一个对象提供一个替身或占位符以控制对这个对象的访问。

理解代理模式

代理模式所要解决的问题其实是要控制对某一个对象的访问,这句话怎么理解呢?就好比生活中我们需要去做某些事情,比如买房,出国旅游等,由于我们对买房,出国旅游的相关办理手续不是很熟悉,所以我们希望找一个人帮助我们去解决这个事情。委托代理我们去找相关的机构办理相关的业务。注意,这个时候机构直接面对的对象是我们委托的人,而不是我们本人。虽然很多事情本来是要我们自己完成的,但是代理替我们更加好地完成了。

一些不同的代理控制

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源
  • 保护代理基于权限控制对资源的访问

代理模式类图

代理模式类图

下面分别介绍类图中各个组件的作用:

ProxyRealSubject都实现了Subject接口,这允许任何客户都可以像处理RealSubject对象一样来处理Proxy对象。

Proxy持有Subject的引用,所以必要时它可以将请求转发给Subject

RealSubject就是真实对象啦,也就是实际做事的那个对象,Proxy会控制对RealSubject的访问。

在某些例子中,Proxy还会负责RealSubject对象的创建和销毁。客户和RealSubject的交互都必须通过Proxy。因为ProxyRealSubject实现了想用的接口(Subject),所以任何用到RealSubject的地方都可以用Proxy取代。Proxy也控制了对RealSubject的访问,在某些情况下,我们可能需要这样的控制。这些情况包括RealSubject是远程的对象,RealSubject创建开销大,或者是RealSubject需要被保护。

实例分析-CD封面虚拟代理

我们知道使用CD播放机来播放CD的时候我们可以看到每一首歌曲的封面,其实这里面也是使用到了代理模式。现在其实播放CD的很少了,不过很久以前非常流行,那个时候电脑内存小,磁盘速度慢,因此打开一张图需要缓冲一段时间,因此需要在加载的时候给用户提供一个不错的提示,能让他“耐心”地等一会儿。

先展示这个CD封面加载模式的类图结构吧:

CD封面虚拟代理模式

简单说明一下系统的逻辑,我们可以看到,代理ProxyImage比真实对象RealImage少了一个方法loadFromDisk()。这个非常重要,说明代理对象最终的目的是为了控制这个对象的访问而不是改变或者新增这个对象的行为。

创建一个Image接口:

1
2
3
4
5
6
7
8
9
10
package com.qinjiangbo.proxy;

/**
* @date: 23/12/2017 9:44 PM
* @author: [email protected]
* @description:
*/
public interface Image {
void display();
}

然后先实现RealImage类:

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
package com.qinjiangbo.proxy;

/**
* @date: 23/12/2017 9:45 PM
* @author: [email protected]
* @description:
*/
public class RealImage implements Image {

private String fileName;

public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}

@Override
public void display() {
System.out.println("Displaying " + fileName);
}

private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}

再实现ProxyImage类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.qinjiangbo.proxy;

/**
* @date: 23/12/2017 9:46 PM
* @author: [email protected]
* @description:
*/
public class ProxyImage implements Image {

private RealImage realImage;
private String fileName;

public ProxyImage(String fileName){
this.fileName = fileName;
}

@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}

实例测试

写一个带有main函数的类进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.qinjiangbo.proxy;

/**
* @date: 23/12/2017 9:47 PM
* @author: [email protected]
* @description:
*/
public class Main {

public static void main(String[] args) {
Image image = new ProxyImage("yesterday-once-more.png");

// image will be loaded from disk
image.display();
// separate line
System.out.println("==================================");
// image will be loaded directly
image.display();
}
}

结果如下:

1
2
3
4
Loading yesterday-once-more.png
Displaying yesterday-once-more.png
==================================
Displaying yesterday-once-more.png

装饰者模式 vs 代理模式

其实这个两个模式非常容易被人搞混,为什么呢?因为这两个模式在结构上几乎没什么区别。我们再来回顾一下装饰者模式的类图:

装饰者模式类图

可以看到装饰者模式和代理模式都是需要实现一个公共的接口,不管是装饰者与被装饰者还是代理者与被代理者。当然还是有很多不一样的地方的,我们细细道来:

  1. 代理模式中只是对真实对象的“代表”,而不是“装饰”对象。
  2. 代理模式和装饰者模式虽然结构很像,但是目的意图完全不同。
  3. 比较重要的一点是代理模式能够实例化它代理的对象,而装饰者模式不行,它只能从外部传入进来。

远程方法调用

Java中的远程方法调用调用就是代理模式的典型代表,我们可以看一下这个过程:

  • 客户端调用某个对象的方法,代理接收到这个请求以后将相关方法、参数等信息通过序列化的方式发送给服务端,服务端收到以后完成本地方法调用。
  • 服务端调用完成以后,返回给本地代理,代理奖将结果进行序列化传递给客户端,客户端收到结果,就像在本地一样,完全无感知。

客户端发送请求
客户端接收结果

总结

代理模式非常的重要,以至于我后面还会重点写一篇文章来介绍代理模式在Java中的几种典型代表。它在我们的生产中有着非常重要的作用,比如我们最常见的Dubbo,HSF等RPC框架,都是基于代理模式来实现的。掌握好代理模式,我们也能自己实现一个RPC框架,这对于我们理解网络,代理,反射等各种技术要点理解都是有非常重要的提升和锻炼作用的。

分享到