本文微信公众号「安卓小煜」首发
1. 代理模式
代理模式英文全称为 Proxy Pattern,也被称为委托模式,属于结构型设计模式的一种。
2. 定义
为其他对象提供一种代理以控制对这个对象的访问
3. 使用场景
当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问
为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口
4. UML 类图
这个是我用 PlantUML 画的,关于 PlantUML,可以看我之前文章的介绍: 画流程图你还在用鼠标拖吗
这里我们的 Client 是客户端,委托对象 RealSubject 和代理对象 ProxySubject 都实现了相同的接口(这里是抽象类) Subject。
对应的代码仓库见:Proxy Pattern Repo
5. 代理模式分类
代理模式分为静态代理和动态代理
5.1 静态代理
代理类的 class 编译文件在代码运行前就已存在
5.2 动态代理
通过反射机制动态地生成代理者的对象,代理谁在执行阶段决定
6. 实战演练
我们通过一个实际的例子来演示静态代理和动态代理是如何通过代码来实现的
6.1 场景说明
小李以前在公司上班时,遇到老板拖欠工资的事情,小李想通过法律途径来解决该问题。
由于小李没有专业的法律相关知识,因此需要一个律师来作为自己的诉讼代理人。
6.2 静态代理实现
6.2.1 公共接口
我们首先需要定义一个代理类和委托类共同实现的接口,对应我们上文的 Subject,在这里我们定义接口为 ILawsuit。
public interface ILawsuit {
// 提交申请
void submit();
// 进行举证
void burden();
// 开始辩护
void defend();
// 诉讼完成
void finish();
}
6.2.2 委托类
接着我们定义委托类实现接口里面的具体功能,对应我们上文的 RealSubject,在这里我们定义接口为 Lee。
public class Lee implements ILawsuit {
@Override
public void submit() {
System.out.println("老板拖欠工资!特此申请仲裁!");
}
@Override
public void burden() {
System.out.println("这是合同书和过去一年的银行工资流水!");
}
@Override
public void defend() {
System.out.println("证据确凿!不需要再说什么了!");
}
@Override
public void finish() {
System.out.println("诉讼成功!判决老板即日起七天内结算工资");
}
}
6.2.3 代理类
接着我们定义代理类实现接口里面的具体功能,但是这里我们是通过调用委托类来达到实现接口具体功能的结果,对应我们上文的 ProxySubject,在这里我们定义接口为 Lawyer。
public class Lawyer implements ILawsuit {
// 代理类持有委托类的引用,以便调用委托类来实现接口的具体功能
private ILawsuit mLawsuit;
public Lawyer(ILawsuit lawsuit) {
this.mLawsuit = lawsuit;
}
@Override
public void submit() {
mLawsuit.submit();
}
@Override
public void burden() {
mLawsuit.burden();
}
@Override
public void defend() {
mLawsuit.defend();
}
@Override
public void finish() {
mLawsuit.finish();
}
}
6.2.4 客户端
接着我们定义客户端来演示下效果,对应我们上文的 Client,在这里我们定义为 StaticProxyClient。
public class StaticProxyClient {
public static void main(String[] args) {
ILawsuit lee = new Lee();
ILawsuit laywer = new Lawyer(lee);
laywer.submit();
laywer.burden();
laywer.defend();
laywer.finish();
}
}
结果输出为:
老板拖欠工资!特此申请仲裁!
这是合同书和过去一年的银行工资流水!
证据确凿!不需要再说什么了!
诉讼成功!判决老板即日起七天内结算工资
对应的代码仓库见:Static Proxy Repo
6.3 动态代理实现
上一节我们通过小李的例子讲述了静态代理的实现方式,如果我们用动态代理来实现的话,怎么实现,哪里需要修改呢?
首先公共接口不需要修改,其次委托类也不需要修改。
需要修改的是代理类和客户端。
6.3.1 公共接口
同 6.2.1,此处不再赘述
6.3.2 委托类
同 6.2.2,此处不再赘述
6.3.3 动态代理
我们定义动态代理,通过反射来调用委托类对应的接口方法。在这里我们定义接口为 DynamicProxy。
public class DynamicProxy implements InvocationHandler {
// 代理类持有委托类的引用,以便调用委托类来实现接口的具体功能
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用委托类的方法
Object result = method.invoke(obj, args);
return result;
}
}
可以看到,我们这里的代码和静态代理 6.2.3 的实现 Lawyer 相比简洁了不少。
当然,大家应该注意到了,我们这里是动态代理而不是代理类,原因在于动态代理的代理类是动态构建的,所以它的体现在我们下文的 6.3.4 中。
6.3.4 客户端(代理类在此构建)
我们定义客户端来演示下效果,对应我们上文的 Client,在这里我们定义为 DynamicProxyClient。
代理类为我们动态构建的对象 lawyer 所对应的类,对应我们上文的 ProxySubject。
public class DynamicProxyClient {
public static void main(String[] args) {
// 构建委托类
ILawsuit lee = new Lee();
// 将委托类传入动态代理
DynamicProxy dynamicProxy = new DynamicProxy(lee);
// 获取委托类的 ClassLoader
ClassLoader loader = lee.getClass().getClassLoader();
// 动态构建代理类
ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader,
new Class[]{ILawsuit.class}, dynamicProxy);
lawyer.submit();
lawyer.burden();
lawyer.defend();
lawyer.finish();
}
}
结果输出为:
老板拖欠工资!特此申请仲裁!
这是合同书和过去一年的银行工资流水!
证据确凿!不需要再说什么了!
诉讼成功!判决老板即日起七天内结算工资
对应的代码仓库见:Dynamic Proxy Repo
6.4 静态代理和动态代理的适用场景
静态代理通常适用于以下场景:
- 代理对象较少:当系统中代理对象数量较少,且代理逻辑相对固定时,可以使用静态代理。
因为静态代理需要手动编写代理类,所以代理对象较多时会导致代码量增加。 - 编译时确定代理逻辑:如果代理逻辑在编译时就能确定,且不需要在运行时改变,那么静态代理是一个很好的选择。它简单明了,易于理解。
动态代理则更适用于以下场景:
- 代理对象较多:当系统中存在大量代理对象,且代理逻辑较为相似时,动态代理能够减少代码量,提高开发效率。
- 运行时改变代理逻辑:动态代理可以在运行时动态地改变代理逻辑,这使得它在处理复杂业务逻辑和变化的需求时更具优势。例如,在AOP(面向切面编程)中,可以通过动态代理实现横切关注点(如日志记录、事务管理等)的织入。
参考:
Android 源码设计模式解析与实战