名称
代理模式(PROXY),别名:Surrogate
目的
为其他对象提供一种代理以控制对这个对象的访问
适用性
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一些可以使用Proxy模式常见情况:
远程代理(RemoteProxy),为一个对象在不同的地址空间提供局部代表。远程代理用于在网络中的不同地址空间之间进行通信,为远程对象提供一个本地的代理对象,使得客户端可以像操作本地对象一样操作远程对象。这种代理也被称为“大使”。
虚代理(Virtual Proxy),根据需要创建开销很大的对象。虚代理的核心思想是先创建一个轻量级的代理对象,这个代理对象在被请求时才去加载或创建真正的对象。这样可以在不使用这些对象时节省资源,并且只在真正需要时才进行加载。
保护代理(Protection Proxy)控制对原始对象的访问。保护代理可以通过代理对象来实现对原始对象的访问控制,例如基于用户身份验证、授权或日志记录等功能。保护代理允许开发者在不修改原始对象的基础上添加额外的安全性和控制逻辑。
智能指引(Smart Reference),取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:
对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为SmartPointers[Ede92])。
当第一次引用一个持久对象时,将它装入内存。
在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
结构
代理(Proxy):
保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同,Proxy会引用Subject。
提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
控制对实体的存取,并可能负责创建和删除它。
其他功能依赖于代理的类型:
远程代理:负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
虚代理:可以缓存实体的附加信息,以便延迟对它的访问。
保护代理:检查调用者是否具有实现一个请求所必需的访问权限。
智能指引:在访问对象时执行一些附加操作。
主题(Subject):定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
真实主题(RealSubject):定义Proxy所代表的实体,实现了主题。
协作
代理根据其种类,在适当的时候向RealSubject转发请求。
效果
优点
Proxy模式在访问对象时引入了一定程度的间接性。根据代理的类型,附加的间接性有多种用途
远程代理可以隐藏一个对象存在于不同地址空间的事实。(远程访问)
虚代理可以进行最优化,例如根据要求创建对象。(延迟加载)
保护代理和智能指引都允许在访问一个对象时有一些附加的内务处理。(访问控制、附加操作)
Proxy模式还可以对用户隐藏另一种称之为copy-on-write的优化方式。
Copy-on-write(COW,写时复制)是一种优化技术,主要用于减少内存的使用,尤其是在多线程或多进程环境中。这种技术的基本思想是在多个对象共享同一份数据时,只有当某个对象真正需要修改这份数据时,才会复制一份新的数据副本供该对象使用。这样可以避免不必要的数据复制,从而节省内存资源。COW可以大幅度的降低拷贝庞大实体时的开销。
在代理模式中,可以通过使用COW技术来进一步优化内存使用,特别是当代理对象需要管理的数据较大时。代理模式可以用来隐藏COW的实现细节,使得客户端无需关心底层数据是如何管理和优化的
。
降低耦合度。代理模式使得客户端和真实对象之间解耦,客户端只需要与代理对象交互,这降低了系统各部分之间的依赖。
隐藏实现细节。代理对象可以隐藏目标对象的具体实现细节,保护目标对象的安全性。
符合开闭原则。客户端可以根据抽象主题角色进行编程,增加和更换代理类无需更改源代码,符合开闭原则,系统拥有较好的灵活性。
缺点
增加复杂性。引入代理对象会增加系统的复杂性,可能需要额外的代码来管理代理和真实对象之间的关系。
性能损失。由于引入了代理对象,访问真实对象的过程可能会稍微减慢,特别是在代理中执行了额外操作的情况下。
维护成本。随着代理功能的增加,维护代理类的成本也会增加,特别是在需要更新多个代理类时。
过度设计的风险。如果不当使用代理模式,可能会导致过度设计,增加了不必要的复杂性和维护负担。
应用
问题
校园故事中常常会有这样的场景,男主喜欢某个女主,想徐徐图之,就找到她闺蜜,让女主的闺蜜代替自己送礼物并说些好话,这里就用到了代理模式的思想。不管是男主还是她闺蜜,都可以抽象出一个送礼物的人,在代理模式中就是主题,定义了三个方法,送书、送巧克力、送花;男主在代理模式中就属于真实主题,实现主题的三个方法;而女主闺蜜就是代理,她代理男生执行这三个方法,所以也需要重写这三个方法,方法内部调用男主的方法,当然她可能也会添油加醋,说好说坏就看心情了。
示例
UML
代码示例
主题:送礼物的人
package com.ysj.part4.proxy.subject;
public interface Giver {
/**
* 送花
*/
void giveFlowers();
/**
* 送巧克力
*/
void giveChocolate();
/**
* 送书
*/
void giveBook();
}
真实主题:男主,实现Giver
package com.ysj.part4.proxy.realSubject;
import com.ysj.part4.proxy.subject.Giver;
public class Man implements Giver {
public void giveBook() {
System.out.println("送你一本书....");
}
public void giveChocolate() {
System.out.println("送你一盒巧克力....");
}
public void giveFlowers() {
System.out.println("送你一束花....");
}
}
代理:闺蜜,实现Giver ,oilAndVinegar非必须
package com.ysj.part4.proxy.proxy;
import com.ysj.part4.proxy.realSubject.Man;
import com.ysj.part4.proxy.subject.Giver;
public class HerChum implements Giver {
Man man;
// 添油加醋的油和醋
private String oilAndVinegar;
public HerChum() {
this.oilAndVinegar = "闺蜜:好闺蜜,有个傻子";
man = new Man();
}
public void giveBook() {
System.out.print(oilAndVinegar);
man.giveBook();
}
public void giveChocolate() {
System.out.print(oilAndVinegar);
man.giveChocolate();
}
public void giveFlowers() {
System.out.print(oilAndVinegar);
man.giveFlowers();
}
}
客户端:客户端
package com.ysj.part4.proxy;
import com.ysj.part4.proxy.proxy.HerChum;
/**
* 代理模式
* 为其他对象提供一种代理以控制对这个对象的访问
*/
public class Client {
public static void main(String[] args) {
// 男主找到女主闺蜜
HerChum chum = new HerChum();
// 闺蜜找到女主,转送了书
chum.giveBook();
// 闺蜜找到女主,转送了巧克力
chum.giveChocolate();
// 闺蜜找到女主,转送了花
chum.giveFlowers();
}
}
已知应用
Spring Framework
AOP(面向切面编程):Spring 使用代理模式来实现 AOP,通过动态代理为对象添加横切关注点(如日志记录、性能监控等)。
事务管理:Spring 通过代理模式为数据访问对象(DAO)提供事务管理支持,使得开发者可以透明地使用事务。
MyBatis
动态代理:MyBatis 使用 JDK 动态代理或 CGLIB 来实现接口的动态代理,以支持 SQL 映射和结果映射。
延迟加载:MyBatis 支持延迟加载,通过代理模式实现,只有当真正需要时才加载关联对象。
CGLIB
动态代理:CGLIB 通过继承目标类的方式生成代理类,适用于无法使用 JDK 动态代理的情况(如目标类没有实现接口)。
评论区