代理模式

代理模式(Proxy Pattern) 又称委托模式,是常见且重要的结构型设计模式之一。

代理在我们生活中比较常见,如访问指定网络的上网代理(代理服务器);又如帮你打包午饭的外卖也是一种代理。

定义

为其它对象提供一种代理以控制对这个对象的访问

使用场景

无法或不想直接访问某个对象时可以通过一个代理 对象来间接访问为了使用的透明性,委托对象与代理对象需要实现相同的接口

UML

Subject: 抽象主题,声明真实主题与代理的共同接口方法,可以是抽象类也可以是接口RealSubject: 真实的主题类,也称被委托类或者被代理类,该类定义了代理所表示的真实对象,执行具体的业务逻辑ProxySubject: 代理类,也称委托类,该类持有一个对真实主题类的引用,在其所实现的接口方法中调用真实主题类对应的方法,以起到代理的作用Client:客户类

Code

说李四在张三的公司上班,任劳任怨但是工资却没拿到。没办法,只能拿起法律武器来保护自己,可是李四又不懂法律和司法流程,这个时候就需要请一个律师来作为自己的诉讼代理人。我们先来看一下诉讼的流程:

// 诉讼接口类interface ILawsuit {    // 提交申请    fun submit()    // 进行举证    fun burden()    // 开始辩护    fun defend()    // 诉讼完成    fun finish()}

这就是诉讼的一般流程,接下来是李四,是具体的诉讼人

// 李四:需要参与这个流程class LiSi : ILawsuit {    override fun submit() {        println("因老板拖欠工资,申请仲裁!")    }    override fun burden() {        println("劳动合同及过去一年的工资卡银行流水!")    }    override fun defend() {        println("证据确凿,没什么好辩护了!")    }    override fun finish() {        println("诉讼成功,判决老板7天内补发工资!")    }}

李四这个类很简单,把具体流程要做的事输出一下,但是具体什么时候才能说话,递交材料还是得专业的来。

// 代理律师class Lawyer(    // 委托人    var trustor: ILawsuit    ) : ILawsuit {    override fun submit() {        trustor.submit()    }    override fun burden() {        trustor.burden()    }    override fun defend() {        trustor.defend()    }    override fun finish() {        trustor.finish()    }}

如前所说,律师类里持有委托人(李四),然后在相应的流程里指导委托人做相应的事。这里简单演示就直接调用相关方法输出,实际情况复杂的多(并没有说律师职业很简单的意思)。

最后就是在客户类调用具体方法来执行整个流程。

fun main() {    val lisi = LiSi()    val zhangwei = Lawyer(lisi)    zhangwei.submit()    zhangwei.burden()    zhangwei.defend()    zhangwei.finish()}// 输出结果:因老板拖欠工资,申请仲裁!劳动合同及过去一年的工资卡银行流水!证据确凿,没什么好辩护了!诉讼成功,判决老板7天内补发工资!

我们看到,李四请了律师张伟,然后就整个流程就全权交给他就行了。

大家可以看到,代理模式很简单,其主要就是一种委托机制,代理干净利落的调用被代理的方法,所以代理模式也叫委托模式。

静态代理

代理模式还可以细分为静态代理和动态代理。静态代理如上述示例代码那样,代理者的代码由程序员编写或自动化程序生成固定的代码,再进行编译。即在代码运行前代理类的相关 class 编译文件就已经存在了。

动态代理

相反,通过反向机制动态地生成代理者的对象就叫动态代理。也就是说我们在 code 阶段就不需要知道代理谁,具体要代理谁会在执行阶段决定。这里 Java 也给我们提供了一个现成的动态代理接口 InvocationHandler

package java.lang.reflect;public interface InvocationHandler {    public Object invoke(Object proxy, Method method, Object[] args)        throws Throwable;}

实现该接口,我们通过 invoke 方法来调用具体的被代理方法。动态代理的代码逻辑更加简洁:

// 动态代理类class DynamicProxy(var obj: Any): InvocationHandler {    override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {        // kotlin 调用java 可变参数的兼容写法        return method.invoke(obj, *(args ?: emptyArray()))    }}

我们在代理里持有一个 Object 对象(kotlin 语法中是 Any), 这个 obj 对应前文的委托人(trustor)。而我们调用被代理类的具体方法则在 invoke 方法中执行。最后修改一下最终客户类的代码调用。

fun main() {    val lisi = LiSi()    val proxy = DynamicProxy(lisi)    // 获取被代理类的 ClassLoader    val loader = lisi.javaClass.classLoader    // 动态构建一个代理律师    val lawyer = Proxy.newProxyInstance(        loader, arrayOf(ILawsuit::class.java), proxy    ) as ILawsuit // 强转    // 流程    lawyer.submit()    lawyer.burden()    lawyer.defend()    lawyer.finish()}// 输出结果与静态代理一致,不再赘述

动态代理通过一个代理类来代理 N 多个被代理类,其实质是对代理者与被代理者进行解耦。相对而言静态代理则只能为给你写接口下的实现类做代理,不过也因此静态代理更加符合面向对象原则 。

按范围分类

静态代理和动态代理是从编码方面来区分代理模式的两种方式。我们也可以从其适用的方法来区分不同类型的代理实现:

远程代理 (Remote Proxy): 为某个对象在不同的内存地址提供局部代理,使系统可以将 Server 部分的实现隐藏虚拟代理 (Virtual Proxy): 使用一个代理对象表示一个十分耗资源的对象并在真正需要时才创建真正的对象保护代理 (Protection Proxy): 使用代理控制对原始对象的访问,常用于权限限制智能引用(Smart Reference): 在访问原始对象时执行一些自己的附加操作并对指向原始对象的引用计数

基于不同的类型区分,静态和动态代理都可以应用于上述四种情形,这里仅做简单介绍。

Android 源码中的实现

Android 里有不少的代理模式的实现,比如 ActivityManagerProxy 代理类,其具体代理的是 ActivityManagerNative 的子类  ActivityManagerService 即我们熟悉的 AMS 。下面我们来看一下这个代理模式中涉及到的几个类:

(图源: https://www.cnblogs.com/bastard/archive/2012/05/25/2517522.html)

IActivityManager 作为 ActivityManagerProxy 和 ActivityManagerNative 的公共接口,两个类具有部分相同的接口,可以实现合理的代理模式;ActivityManagerProxy 代理类是 ActivityManagerNative 的内部类;ActivityManagerNative 是个抽象类,真正发挥作用的是它的子类 ActivityManagerService(系统组件)。ActivityManager运行在一个进程里面,ActivityManagerService 运行在另一个进程内,此处是采用 Binder 机制实现跨进程通信;所以此处的代理模式的运用属于:「远程代理」

总结

代理模式应用广泛,我们讲的其它形式的结构型模式中,有些可以看到代理模式的影子,甚至可以说是代理 模式的一种针对性优化。而代理模式是细分化至很小的一种模式,几乎没有什么缺点,硬要说的话就是设计模式的通病:对类的增加,可忽略不计。

End 吹斯汀

在地球上看金星的运行轨迹每8年会画一个五角星哈哈,无聊的知识又增加了~~推荐阅读:

Android 自定义 View | 剑气加载

效率 | macOS 双击安装 APK

哔哩哔哩在Hilt组件化的使用 | 技术探索

JetPack 开篇 | App Startup 的使用及原理

组合模式

卡顿、ANR、死锁,线上如何监控?

Android IO监控 | 性能监控系列

点赞在看年薪百万