设计模式之代理模式

设计模式之代理模式

代理模式简介

代理设计模式属于结构设计模式类别,它是软件开发中最常用的模式之一。 此模式有助于控制连接资源的使用和访问行为。 称为“代理”的单独对象有助于在客户端和原始服务对象之间建立连接。 因此,代理将充当实际原始对象的替代或占位符对象,尤其是控制访问。 原始服务对象的性质可能是创建过程中的高成本或需要安全访问。 系统的核心行为不会改变,只有内部组件的结构和控制会进行修改以获得更好的结果。

使用代理模式创建代表(representative)对象,让代理对象控制某对象的访问,被代理的对象是远程的对象、创建开销大的对象或需要安全控制的对象。

GoF定义,“为另一个对象提供代理或占位符以控制对其的访问”。

模式引入了替代而不是实际的原始对象来与外部实体进行交互。 您正在通过另一个对象访问该原始对象,同时保留了整个系统的行为。

现实生活中的例子

我们可以在金融系统中找到许多代理模式的例子。 信用卡将充当提供相同功能的实际银行界面。 它将充当自动柜员机的提款,支票余额,转账和其他几项便利的工作,这些工作由信用卡代理完成。 另外,支票可以代表个人执行汇款功能。 它是银行批准后从一个人的帐户向另一个人支付特定金额的命令。 在这里,支票充当了一大笔现金的代理。

面临的问题

这发生在所需对象不太容易初始化或访问的情况下。 如果该对象驻留在远程库中,或者它消耗大量系统资源,并且客户端仅在特定条件下需要它。 因此,初始化和维护此类对象不是系统的最有效方法。 如果需要在访问实际服务对象时提供其他功能,则将为开发人员带来另一层解决方法。 因此,系统需要找到更好的解决方案来处理这种情况。

解决方案

通过引入代理可以解决上述问题:代替昂贵且受保护的实际对象的替代组件。 该代理将包含与原始服务对象相似的接口,并且当它接收到客户端请求时,代理对象将创建原始对象的实例并通过响应进行委托。

什么是代理?

代理只是原始对象的替代对象。 它也充当包装器或代理对象,客户端调用该包装器或代理对象以访问后台的原始对象。 代理是一个轻量级对象,它实现与原始实际对象相同的接口,并控制对实际对象的访问。 如果需要,它可以包括其他功能,例如在对实际对象的操作占用大量资源时进行缓存,并在调用实际对象操作之前确保前提条件。

为什么我们使用代理?

  • 代理可以根据需要执行不同的操作。 它可以执行诸如日志记录和过滤请求之类的预处理,然后再移交给原始对象以完成某些前提条件。
  • 同样,代理可以在将结果发送回请求者之前执行后处理。 当需要覆盖功能时,可以使用代理。
  • 特别是,当需要打破许可证以防止破坏现有的旧系统时,代理将是一个不错的选择。
  • 当需要用于资源密集型应用程序的兑现机制以减少网络流量和成本时,代理是不错的选择。
  • 当存在昂贵且复杂的安全问题时,代理可用于承受系统的安全操作。

代理模式变化

有几种类型的代理模式变化。

  • 远程代理:顾名思义,这些代理在不同的工作空间上工作。 那是实际的原始对象存在于远程地区。 远程代理充当远程对象的本地代表,因此,客户端不了解原始对象的远程性。 Java RMI技术中的“存根”对象就是这种模式的一个很好的例子。 当客户端需要获取原始对象的服务时,远程原始对象将驻留在其他JVM的堆中,存根将充当代理以交互和调用远程对象的方法。
  • 虚拟代理:这是节省应用程序成本的一种方法。 在这种情况下,实际原始对象的创建非常昂贵,并且内存和资源消耗很高。 为了避免这种障碍,应用程序引入了虚拟代理。 虚拟代理充当昂贵的原始对象的占位符,并仅在需要时创建原始对象的真实对象。 通过这种方式,虚拟代理可以保存实际对象并在以后的调用中重新使用,从而防止对象重复并节省内存。
  • 保护代理:该代理为原始对象添加了一层保护。 在这种情况下,原始对象受不同的访问级别保护。 当客户端要访问原始对象时,保护代理将在转发请求之前检查客户端的适当访问级别。
  • 智能代理:该代理提供了附加的安全层。 它执行其他操作以验证原始对象是安全的,可以避免意外访问和威胁,例如意外处置和删除原始对象,在访问原始对象之前检查原始对象是否已锁定以及在首次将持久对象加载到内存时参考。

代理设计模式的类图

代理设计模式的组件

  • Proxy:控制对真实主题的访问。 代理包含真实主题的实例,并提供与主题相同的接口,以便轻松联系真实主题。 代理类中可以有其他方法来执行中间工作。
  • Subject:这是真实主题和代理的通用接口。 因此,代理和真实主题都实现了“主题”界面。 客户端仅与代理进行交互以访问真实主题,因为可以随时使用代理代替真实主题。
  • RealSubject:这是实际的原始对象,可以通过代理进行访问。 这可能是网络连接,文件,内存中的大对象或其他昂贵或难以创建的组件

代理模式如何工作?

在此模式下,客户端不直接与原始对象进行交互,而是,客户端使用代理对象来调用原始对象。 但是最重要的一点是客户端不了解代理,并且代理的行为与客户端的原始对象相同。

代理模式示例

假设在自然公园中有一种非常稀有的动物叫做“海熊”。 并非每个公园游客都可以参观海熊。 只有生物学家,兽医和动物研究人员这样的授权人员才能访问他们。 为了控制这种稀有物种的进入,公园任命了“海洋熊卫队”。 他将评估访问者的详细信息,并酌情控制对海熊的访问。

让我们以代理模式实现这种情况,并确定相关组件。

SeaBearGuardProxy :这是SeaBearOriginal类的代理类。

BearProtectInterface : 这是SearBearOriginal和SeaBearGuardProxy类共享的接口,并且包含访问控制机制。

SeaBearOriginal : 这是代表自然公园中稀有动物物种的实际原始类别

NatureParkVisitorClient: 这是来参观海熊的游客客户端

实现代理模式步骤

  1. 标识实际的原始对象,该对象需要通过代理进行包装。
  2. 确定需要执行哪些步骤来控制对原始对象的访问。
  3. 创建一个可由原始对象和代理实现的合适接口。
  4. 创建具有访问控制机制以及支持代理意图的其他功能的代理。
  5. 创建客户端对象并通过代理访问原始对象

代理模式代码示例

BearProtectInterface.java

1
2
3
4
5
6
7
8
/**
* 主题接口
* @author Mr.zxb
* @date 2020-09-13 15:45:19
*/
public interface BearProtectInterface {
void allowVisit(int visitorCode);
}

SeaBearGuardProxy.java

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
/**
* 代理对象
*
* @author Mr.zxb
* @date 2020-09-13 15:48:09
*/
public class SeaBearGuardProxy implements BearProtectInterface {

private int visitorCode;
private String visitorProfession;
private SeaBearOriginal seaBear = new SeaBearOriginal();

@Override
public void allowVisit(int visitorCode) {
if (visitorCode == 1) {
System.out.println("Visitor allowed to see the Sea Bear");
seaBear.showSeaBear();
} else {
System.out.println("Visitor NOT allowed to see the Sea Bear");
}
}

public void assignVisitorCode(String profession) {
if (visitorProfession == "Biologist" || visitorProfession == "Vet" || visitorProfession == "Animal Researcher") {
visitorCode = 1;
} else {
visitorCode = 0;
}
}
}

SeaBearOriginal.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 原始对象
* @author Mr.zxb
* @date 2020-09-13 15:49:19
*/
public class SeaBearOriginal implements BearProtectInterface {
@Override
public void allowVisit(int visitorCode) {
if (visitorCode == 1 ) {
System.out.println("Visitor allowed to see the Sea Bear");
this.showSeaBear();
} else {
System.out.println("Visitor NOT allowed to see the Sea Bear");
}
}

public void showSeaBear(){
System.out.println("You are welcome to the Sea Bear premisses");
}
}

NatureParkVisitorClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 客户端对象
* @author Mr.zxb
* @date 2020-09-13 15:51:53
*/
public class NatureParkVisitorClient {
public static void main(String[] args) {
// 创建代理对象
BearProtectInterface seaBearProxy = new SeaBearGuardProxy();
try {
// 调用接口
seaBearProxy.allowVisit(1);
} catch (Exception e) {
e.printStackTrace();
}
}
}

示例代码时序图

使用代理模式的场景

  • 当原始对象创建成本很高时
  • 当原始对象存在于远程环境中时
  • 当原始对象上的安全控制受到限制时
  • 在创建原始对象之前和之后需要执行其他操作时
  • 当按需创建原始对象或系统需要在加载某些资源时延迟
  • 当原始对象位于旧系统或第三方库中时

总结

  • 代理模式为另一个对象提供代表,以便控制客户对对象的访问,管理访问的方式有许多种
  • 远程代理管理客户和远程对象之间的交互
  • 虚拟代理控制访问实例化开销大的对象
  • 保护代理基于调用者控制对对象方法的访问
  • 代理模式有许多变体,例如:缓存代理、同步代理、防火墙代理和写入时复制代理
  • 代理在结构上类似装饰者,但是目的不同
  • 装饰者模式为对象加上行为,而代理则是控制访问
  • Java内置的代理支持,可以根据需要建立运行时动态代理,并将所有调用分配到所选的处理器
  • 就和其他的包装者(wrapper)一样,代理会造成你的设计中类的数目增加