以下事务场景不大现实,我这儿只是供给一种思路

想象一种场景:有一天,产品司理让你记载某些当地的行为日志并且存储到本当地便查阅,你或许会写下如下代码:

interface ILogger {
    fun logInfo(action: String)
    fun logError(action: String)
}
class Logger : ILogger{
    override fun logInfo(action: String) {
        //存储到本地
        saveToLocalFile(action)
    }
    override fun logError(action: String) {
        //存储到本地
        saveToLocalFile(action)
    }
    private fun saveToLocalFile(action: String) {}
}

当需求调用的时分:

val logger: ILogger = Logger()
logger.logError("出现问题")

当然了,你更大概率是考虑用一个单例类直接调用,而不是每次都这样写。
假设某天换了个产品司理,要求你在这些存储日志之前,先将日志上传到服务器,存储日志后,做一个埋点记载

class Logger : ILogger{
    override fun logInfo(action: String) {
        //上传到服务器
        upLoadToCloud(action)
        //存储到本地
        saveToLocalFile(action)
        //埋点
        eventTracking(action)
    }
    override fun logError(action: String) {
        //上传到服务器
        upLoadToCloud(action)
        //存储到本地
        saveToLocalFile(action)
        //埋点
        eventTracking()
    }
    private fun saveToLocalFile(action: String) {}
    private fun upLoadToCloud(action: String) {}
    private fun eventTracking() {}
}

规划形式考究一个职责单一,那么以上代码最直观的便是不同的功用耦合在一起。

什么是署理形式

一句话解释便是:在不改动原有功用的基础上,经过署理类扩展新的功用,使得功用之间解耦,或者结构和事务之间解耦,有点装修器形式的滋味。

静态署理

interface ILogger {
    fun logInfo(action: String)
    fun logError(action: String)
}
class Logger : ILogger{
    override fun logInfo(action: String) {
        //存储到本地
        saveToLocalFile(action)
    }
    override fun logError(action: String) {
        //存储到本地
        saveToLocalFile(action)
    }
    private fun saveToLocalFile(action: String) {}
}
class LoggerProxy(val logger: Logger) : ILogger {
    override fun logInfo(action: String) {
        //上传到服务器
        upLoadToCloud(action)
        //经过传进来的logger目标来调用本来的完成办法
        logger.logInfo(action)
        //埋点
        eventTracking(action)
    }
    override fun logError(action: String) {
        //上传到服务器
        upLoadToCloud(action)
        //经过托付logger目标来调用本来的完成办法
        logger.logError(action)
        //埋点
        eventTracking(action)
    }
    private fun upLoadToCloud(action: String) {}
    private fun eventTracking(action: String) {}
}
//使用办法
val logger: ILogger = LoggerProxy(Logger())
logger.logError("出错了")

在第25行,咱们新添加了一个新的LoggerProxy署理类同样的完成了ILogger接口,在两个办法中,咱们按顺序完成了功用的调用,将上传到服务器和埋点的逻辑和存储到本地的逻辑进行了别离,署理类LoggerProxy在事务的执行前后附加了其他的逻辑。
看到这你或许会觉得,有点脱裤子放屁了。的确,当时代码量特别小,关于当时代码表现的或许不太明显,假如你正在一个规划相对大型的结构,事务和结构代码的别离显得就相对重要了。
作为一种规划思维,他供给的是一种思路,让你写出来的不是面向过程的代码,有好有坏,当然在实际项目中不要为了规划形式而规划形式,不然就拔苗助长了,写出来的代码可读性差。

动态署理

关于静态署理,上面的代码中咱们在署理类中的前后加了两个不同的功用,这两个相对职责不同的功用耦合在了一起,我由于偷闲没将其间的一个功用拆走,正常情况是应该再写一个署理类去做相同的一部分操作,假如功用更多的话就要写更多的署理类,繁琐度可想而知。
再一个,静态署理是在程序运转前就现已存在署理类的字节码文件,署理类和托付类的联系在运转前就确认了。而动态署理类的源码是在程序运转期间由JVM依据反射等机制动态的生成,所以不存在署理类的字节码文件。署理类和托付类的联系是在程序运转时确认。

class Logger : ILogger{
    override fun logInfo(action: String) {
        println("存储到本地: $action")
        saveToLocalFile(action)
    }
    override fun logError(action: String) {
        println("存储到本地: $action")
        saveToLocalFile(action)
    }
    private fun saveToLocalFile(action: String) {}
}
class LoggerProxy(private val target: ILogger): InvocationHandler {
    fun createProxy() = Proxy.newProxyInstance(
        ILogger::class.java.classLoader,
        arrayOf<Class<*>>(ILogger::class.java),
        LoggerProxy(target)
    ) as ILogger
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        val action = args!![0].toString()
        if (method?.name == "logInfo") {
            uploadToCloud(action)
            target.logInfo(action)
            eventTracking(action)
        } else if (method?.name == "logError") {
            uploadToCloud(action)
            target.logError(action)
            eventTracking(action)
        }
        return null
    }
    private fun uploadToCloud(action: String) {
        println("上传数据到服务器")
    }
    private fun eventTracking(action: String) {
        println("埋点")
    }
}
interface ILogger {
    fun logInfo(action: String)
    fun logError(action: String)
}
调用办法
val proxy = LoggerProxy(Logger())
proxy.createProxy().logError("出错了")
打印顺序
1. 上传数据到服务器
2. 存储到本地: 出错了
3. 埋点
  1. 在动态署理中,当咱们经过createProxy()创立署理目标后,调用logError或logInfo办法的时分
  2. 署理目标的invoke()办法会被调用
  3. 由于咱们传入的只要action这个参数,在invoke办法中,可经过args[0]来获取传入的数据;经过method.name获取待执行的办法名,以此来判断逻辑的走向

署理的创立办法createProxy()办法中的代码大部分都是固定的。

总结

静态署理:静态署理在编译时期就现已确认署理类的代码,署理类和被署理类在编译时就现已确认;假如需求扩展的功用越来越多,静态署理的缺点很明显便是要写大量的署理类,办理和维护都不太便利。
动态署理:动态署理在运转时动态生成署理目标,联系灵敏,由所以在运转时动态的生成署理类,动态署理解决了静态署理大量署理类的问题,但是有个新的问题便是反射相对耗时一点。

咱们常用的Retrofit就有用到动态署理,感兴趣的同学可以去深化了解下,这边就不过多讲解,包括AOP(面向切面编程),动态权限申请等等。