以下事务场景不大现实,我这儿只是供给一种思路
想象一种场景:有一天,产品司理让你记载某些当地的行为日志并且存储到本当地便查阅,你或许会写下如下代码:
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. 埋点
- 在动态署理中,当咱们经过createProxy()创立署理目标后,调用logError或logInfo办法的时分
- 署理目标的invoke()办法会被调用
- 由于咱们传入的只要action这个参数,在invoke办法中,可经过args[0]来获取传入的数据;经过method.name获取待执行的办法名,以此来判断逻辑的走向
署理的创立办法createProxy()办法中的代码大部分都是固定的。
总结
静态署理:静态署理在编译时期就现已确认署理类的代码,署理类和被署理类在编译时就现已确认;假如需求扩展的功用越来越多,静态署理的缺点很明显便是要写大量的署理类,办理和维护都不太便利。
动态署理:动态署理在运转时动态生成署理目标,联系灵敏,由所以在运转时动态的生成署理类,动态署理解决了静态署理大量署理类的问题,但是有个新的问题便是反射相对耗时一点。
咱们常用的Retrofit就有用到动态署理,感兴趣的同学可以去深化了解下,这边就不过多讲解,包括AOP(面向切面编程),动态权限申请等等。