代理模式

代理模式

一、什么代理模式?

代理模式(Proxy Design Pattern):在不改变原始类(被代理类)的情况下,对它进行功能的增强。

代理模式有几个特点:

  • 不会改变原始类(被代理类)
  • 核心功能还是由原始类(被代理类)实现
  • 代理类只负责增强的那部分功能

代理,就是帮别人做事情,核心工作还是由被代理人做,但是其他琐碎的事则可以交给代理人。

比如租房时,我们可以找中介帮忙找房子、等一系列事,减少自己的工作量。

这里的中介就是一个代理,我们自己就是被代理人。

二、为什么要用代理模式?

使用代理模式主要有几个目的:

  • 保护目标对象
  • 增强目标对象
  • 隔离功能

保护对象,就是利用代理对象,隔离原始对象的数据,以达到保护对象的目的。

比如说,通过代理类去验证权限,有权限时才可以访问原始对象的数据。

增强对象,就是原始对象功能比较简单,可以通过代理类为其增加额外功能。

比如说,通过代理类去增加缓存,来提高接口的访问速度。

隔离功能,比如隔离业务功能和非业务功能,把非业务功能的代码放在代理类实现。

比如,接口监控、统计、鉴权、限流、事务、幂等、日志等。

三、代理模式怎么用?

3.1 静态代理

静态代理,就是每个被代理对象,都需要编写一个代理类。

这就是说,编写原始类时,必须同时编写一个代理类,否则就没有办法实现代理模式了。

比如说,有100个原始类:Base1Base2 …… Base100

那么,静态代理实现,就需要同时编写 100 个代理类:Proxy1Proxy2 …… Proxy100

(真要这么写,手都得废了~~~~~)

3.2 动态代理

动态代理,就是解决静态代理的问题,改成由程序动态生成代理类,无需自己编写代理类。

比如说,静态代理需要编写100个代理类:Proxy1Proxy2 …… Proxy100

如果是动态代理,只需1个代理生成类即可:ProxyCreator,用来生成不同的代理类。

动态代理,实际就是搞了一个工厂类,专门用于生成代理类,减少手动编写的工作量。

动态代理,虽然不需要自己写代理类,但是一般都需要编写生成动态代理类的代码。

不过像生成动态代理类这种代码,实际上有很多框架都实现了,比如Spring、CgLib等,直接用即可。

3.3 模型结构

代理模型中的角色包括:

  • 抽象主题(SUbject):被代理的目标类接口,或者抽象类
  • 真实主题(RealSubject):被代理目标的实现类
  • 代理角色:代理的实现类,实现了抽象主题接口,内部蕴含真实主题的引用

模型结构:

代理模型结构

示例程序结构:

代理模型结构

抽象主题(Subject):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface Printable {

/**
* 设置名称
* @param name 名称
*/
void setPrinterName(String name);

/**
* 获取名称
*/
String getPrinterName();

/**
* 打印输出
* @param string 输出字符串
*/
void print(String string);

}

真实主题(RealSubject):

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
34
35
36
37
38
39
40
41
public class Printer implements Printable {

private String name;

public Printer() {
heavyJob("Printer 的实例生成中");
}

public Printer(String name) {
this.name = name;
heavyJob("Printer 的实例生成中(" + name + ")");
}

@Override
public void setPrinterName(String name) {
this.name = name;
}

@Override
public String getPrinterName() {
return name;
}

@Override
public void print(String string) {

}

private void heavyJob(String msg) {
System.out.println(msg);
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.print('.');
}
System.out.println("结束。");
}
}

代理角色(Proxy):

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
34
35
36
public class ProxyPrinter implements Printable {

private String name;
private Printer real;

public ProxyPrinter() {}

public ProxyPrinter(String name) {
this.name = name;
}

@Override
public void setPrinterName(String name) {
if (real != null) {
real.setPrinterName(name);
}
this.name = name;
}

@Override
public String getPrinterName() {
return name;
}

@Override
public synchronized void print(String string) {
realize();
real.print(string);
}

private synchronized void realize() {
if (real == null) {
real = new Printer(name);
}
}
}

四、代理模式有什么优缺点?

优点:

  • 代理对象对原始对象起到了保护作用
  • 代理对象可以对原始对象进行扩展

缺点:

  • 每个原始对象都需要有自己的代理类
  • 代理类相当于多了一层中介,而且还增加了功能,接口的访问速度变慢了
  • 系统的复杂度增加了,代码调试变得更麻烦

五、代理模式的形式

  • 远程代理:隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,JAVA 中的 RMI(RemoteMethodInvocation:远程方法调用),调用远程方法接口,就像是在本地调用一样。
  • 虚拟代理:当创建的目标对象开销很大时,只有当真正需要实例时,才会生成和初始化实例。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  • 安全代理:控制不同种类客户对真实对象的访问权限,比如不同客户允许调用的接口不一样。
  • 智能指引:附加一些额外的处理功能。例如,计算接口的访问次数,可以在代理类中处理。
  • 延迟加载:指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

六、和其他模式的对比

6.1 代理 vs. 装饰器

装饰器模式和代理模式的模式结构上很类似,但是它们的使用目的不同:

  • 装饰器模式:偏向于给原始对象增加新的业务功能
  • 代理模式:偏向于给原始对象减轻负担,或者说增加非业务功能
作者

jiaduo

发布于

2022-01-16

更新于

2023-04-03

许可协议