代理模式
简介
最后一种结构型模式了,会赢的
通过代理模式的名字我们也可以了解到这个模式的重要之处就在与这个”代理”二字。我们举几个生活中的例子吧。就比如之前闹的沸沸扬扬的俄乌冲突,其实就是一场代理人战争,乌克兰实际上就是美国的代理人,为什么需要这个代理人呢,因为本人不好下场。换句话说,使用代理人的一般场景就是自己动手的代价是无法接受的,需要通过一种手段来减少损失,这就是代理的关键之处。
再举一个较近的例子。就比如我们去打开一个md文档,这个文档可能会很大,其中包含了一系列的数字和图片,如果我们需要一次性的全部打开的话代价是很大的,可能会感觉到明显的卡顿,这是很影响体验的。为了优化这种,我们可以考虑将这里的系列图片给添加代理。在我们打开时我们不需要去创建图片,而是去通知这些个代理我可能用到这些图片,去让这些代理准备好。直到我们真正需要使用图片(观看,下载等)时才去加载对应的数据。
其实吧,我感觉我们的内存系统中应该就应用到了代理这一概念。让我们想想我们的内存系统是怎么去加载一个程序的。不知道就去学CSAPP吧,小子。当我们点击一个可执行文件或者命令行输入一个指令时。程序调取对应的数据段进入主存。那么,也就是说,我们前面在主存之中最多就保留着对于可执行文件的引用而不是完整的文件。这种引用其实就是一种代理。
接着来看一下我们的页缺失异常的情况。当发生页缺失时,触发对应的信号,程序中断,控制权交还内核,内核进行复制,复制完成后重新把控制权交给程序这个用户。可以看到,这里的内核其实就相当于一个代理人的角色,其代理了所有的程序,当我们的CPU等需要一个程序的信息时,都是通过内核这个代理去进行间接的提取的。通过这样,程序不过去关心这个数据的提取是否麻烦。隔离了用户层与数据层。
定义
为其他对象提供一种代理以控制对这个对象的访问。
这是《设计模式:可复用面向对象软件的基础》(Gang of Four,GoF)中给出的定义。这个定义我们前面其实已经说的很清楚了。这里就不再对其进行赘述了。
我们来看一下在《设计模式:可复用面向对象软件的基础》(Gang of Four,GoF)提高的适用性,其实也是几个具体的例子。
《设计模式:可复用面向对象软件的基础》(Gang of Four,GoF)中指出,代理模式适用于以下场景:
1. 远程代理(Remote Proxy)
当需要访问一个远程对象时,代理可以在本地充当远程对象的代表,客户端可以像操作本地对象一样操作代理。
- 场景:分布式系统或网络通信中,客户端与远程服务之间通过代理进行交互。
- 示例:Java RMI(远程方法调用)或 gRPC 中的客户端存根。现实中的大使馆
2. 虚拟代理(Virtual Proxy)
当创建一个消耗大量资源的对象时,代理可以推迟对象的创建,并在真正需要时才实例化它(即延迟加载)。
- 场景:需要优化性能或延迟加载资源的场景,例如加载大文件、大图片等。
- 示例:
- 加载大图片时,只在显示时才真正加载。
- 数据库连接池中的连接代理。
3. 保护代理(Protection Proxy)
当需要控制对对象的访问权限时,代理可以提供额外的权限检查功能。
- 场景:需要对不同用户角色(如管理员和普通用户)进行访问控制的场景。
- 示例:
- 操作系统中的文件访问控制。
- 公司内部的权限管理系统。
4. 智能引用(Smart Reference)
在访问某个对象时,代理可以在实际操作前后执行一些附加操作,例如记录访问日志、统计引用计数、监控性能等。
- 场景:需要跟踪或管理对象的引用与访问。
- 示例:
- 引用计数机制,自动管理对象的生命周期。
- 日志记录代理,记录对某些重要对象的访问情况。
5. 缓存代理(Caching Proxy)
代理保存一些已有的计算结果,当客户端请求时,直接返回缓存值而不是重新计算。
- 场景:需要频繁访问资源但不希望重复计算的场景。
- 示例:
- 数据库查询结果的缓存。
- 大型计算任务结果的缓存。
6. 防火墙代理(Firewall Proxy)
代理用于保护对象,使其不直接暴露在潜在的风险环境中。
- 场景:需要保护内部资源免受外部非法访问时。
- 示例:
- 网络层中的防火墙代理。
- 内网与外网之间的网关。
总结
GoF 提到的代理模式适用性主要聚焦于以下几点:
- 优化性能(如虚拟代理、缓存代理)。
- 控制访问权限(如保护代理、防火墙代理)。
- 隔离复杂性(如远程代理、智能引用)。
通过代理,访问的灵活性和系统的扩展性得到了极大增强。代理模式的适用性非常广泛,可以根据实际需求选择合适的代理类型。其实吧,这上面的几个代理模式都不重要,没有必要去深入了解,这些都是在一些代理模式中的实际应用场景中抽离出来的普适性质。主要还是得去看UML类图和自己去搓一遍码才能深入理解。
UML类图
可以看到,代理模式对应的类图相对于之前的享元模式等都是算简单的那一批了。
圖中包含三大類別:
**Subject**:是一個抽象類別或介面,定義*
Proxy*和*
RealSubject*的共同介面,當任何有使用*
RealObject*的地方皆能使用*
Proxy`。
***RealSubject***
:被代理的角色,也就是*Proxy*
所代表的真實物件。
***Proxy***
:保存一個參考使得代理可以存取實體,並提供一個與*Subject*
相同的介面,這樣代理就可以用來代替真實物件。
简介
三个类:
Subject类
这个类是所有进行交互的类的父类,也就是说,这是一个定义了接口规范的类,之后的代理人类,幕后人类,用户层类都是使用这个接口规范的。还是那句话,你实际设计中可能没有这个这个类,但是没有这个类不太可能。
RealSubject类
这个类是幕后人类,当然,这些个名字都是我瞎编的,无所谓了。在代理模式中,这个就是那个会被代理的类。通过一个接口规范类的继承可以大大简化我们设计阶段的接口问题,何乐而不为。
Proxy类
这个类就是我们这个类图中的代理人类了,这个类直接与我们的RealSubject类接触。并且,一般来书,其接触的类只会有有限个代理人且不能随便扩展,不然那不就成公交车了。这个类进行了用户层与幕后层之间的直接联系,好处在之前已经说过了就不再赘述。
由于代理模式的UML类图是相对简单的,所以我们更需要靠代码来进行进一步的理解。
代码实例
代理模式的代码简单来写时相当简单的,但是吧,实际的应用场景并没有遇到过,之后有机会再看吧。
这里直接给出三个类的代码,这次不会进行细致分析,只简单过一遍
1 | //抽象主题角色:声明真实主题和代理主题的共同接口。 |
在这个类中,定义了抽象的接口。
1 | //幕后人:定义了代理角色所代理的事物 |
在这个类中去定义实际的行为
1 | //代理主题角色:保存一个引用使得代理可以访问实体,并提供一个与真实主题角色相同的接口,这样代理就可以用来代替真实主题。 |
这就是一个相当简单的代理类的实现了。可以看到,这里简单的设置了一些权限位进行一些标识。通过这些个标识,可以看到当前代理是否存在一些权限,这里获取权限的实现可以交给别的类来做来实现单一职责。
其他其实没什么需要注意的,还有一点,就是这里的代理类跟幕后人类是一个组合的关系。这不是一定的,但是代理人类中需要保留一个幕后人类的这一设定一般来说是一定的。毕竟只有保留了这个简单的引用,我们才能够实现代理。就比如,一个国家的大使馆中必须存在实际的工作人员,那么这个大使馆才能实现这个代理的能力。不然,它就是一座挂着名字的空房而已。
总结
代理模式相对来说是一个相对好理解的结构型设计模式了,通过一层代理封装实现用户层与实现层之间的隔离。其实很多设计模式都可以看到这一点,只是代理模式进行了一定程度上的特化,将这种功能上的封装特化为代理模式而已。
作为看到的最后一种结构型设计模式,其的使用频率跟我们前面创建型设计模式中的原型模式中好像有点相似。毕竟对于代理这种思想的引入是很自然的,就像原型模式在工厂等创建型设计模式中的使用一样。所以啊,得找点项目来搓才能更好的理解这些个设计模式啊