结构型模式之:
- 适配器模式
- 桥接模式
- 组合模式
- 装饰模式
- 外观模式
代理模式- 享元模式
1.什么是代理模式?
代理模式是一种结构型设计模式,其目的是通过代理对象控制对另一个对象的访问。代理对象充当了被代理对象的中间人,客户端通过访问代理对象来间接访问被代理对象,从而可以在访问过程中实现一些额外的控制或逻辑。
为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个加载很多高清图片的对象, 你只是偶尔需要使用它, 并且不是每个对象都要使用它。
你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码。
在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。
2.使用方式
假设有一个接口 Image,定义了显示图像的操作:
public interface Image { void display(); }
然后有一个实际的图像类 RealImage,用于加载和显示这些高清的图像:
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(filename);
}
private void loadFromDisk(String filename) {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
现在,我们希望对图像的显示进行控制,例如限制用户对某些图像的访问。我们可以使用代理模式来实现这个控制逻辑。
首先,我们创建一个代理类 ProxyImage 实现 Image 接口:
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();
}
}
在代理类 ProxyImage 中,当调用 display() 方法时,它会首先检查是否已经创建了 RealImage 对象。如果没有,就会创建一个,并调用 RealImage 对象的 display() 方法来显示图像。
客户端可以直接使用代理对象来加载和显示图像,而无需直接访问实际的图像对象:
public class Client {
public static void main(String[] args) {
// 使用代理对象显示图像
Image image = new ProxyImage("test.jpg");
// 第一次显示图像
image.display();
System.out.println();
// 第二次显示图像,不需要重新加载
image.display();
}
}
通过代理模式,我们可以控制对实际图像对象的访问,并在需要时延迟创建或加载实际的高清图像对象,从而提高系统的性能和安全性。
3.使用场景
从定义和使用场景可以得出,代理模式的产生主要是为了控制访问,来减少相关的多余操作,所以只要是有需要中间层进行控制的场景,都可以用到代理模式,下面是一些常见的场景。
-
延迟初始化(虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。
-
访问控制 (保护代理),如果你只希望特定服务对象,避免被恶意调用, 此时可使用代理模式。
代理可仅在客户端凭据满足要求时将请求传递给服务对象。
-
本地执行远程服务(远程代理)。 适用于服务对象位于远程服务器上的情形。 在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。
-
记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。 代理可以在向服务传递请求前进行记录。
-
缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。
代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。
- 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。
代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。
代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。
通过上面的使用场景,可以发现,大多数跟我们的功能场景不是直接相关,但是多用在项目隔离中,如果你熟悉 Java 语言和 Spring 开发框架,这部分工作都是可以在 Spring AOP 切面中完成的。前面我们也提到,Spring AOP 底层的实现原理就是基于动态代理。
4.优点和缺点
优点:
- 你可以在客户端毫无察觉的情况下控制服务对象。
- 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。
- 即使服务对象还未准备好或不存在, 代理也可以正常工作。
- 开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。
缺点:
- ** 代码可能会变得复杂, 因为需要新建许多类。
- ** 服务响应可能会延迟。