描述问题的Star法则
如果想清晰的描述自己的工作成果,讲出来让人觉得一目了然,最好可以把要讲的东西分为4个部分。
situation:情境,问题出现在何种情况之下
task: 任务,定一个小目标。
action: 针对这个目标,进行行为步骤的细分
result: 在执行了action之后,达成了什么样的结果,是否完成了目标
一句话描述要解决的问题
最近review代码,发现 负责离线包更新的类有点臃肿,上千行的代码量,而且多种逻辑杂糅其中,每次修改都很扎眼。所以思考一下能否优化这个类的结构,最后决定先把 从 离线包的下载,到校验,到解压的流程的核心代码,从原来的类中独立出来,提高程序的可读性和可维护性。
Situation
负责离线包更新的类代码行数过多,可维护性差。这个类负责了离线包的 接口请求,离线包数据的对比,离线包资源的更新(下载,校验,和解压),以及更新完成之后把反馈给外部。责任过大。
Task
定个目标:把 离线包资源的更新(下载,校验,和解压)这部分代码提取出去,单独管理,不再和原来的大类形成耦合关系。更新流程可以单独维护不影响大类。
Action
分析目标:要独立的逻辑是 离线包资源 的下载,校验和解压流程,三者是串联关系,而且后者依赖前者的处理结果,如果前者处理失败,则后续不再执行。符合责任链模式的适用场景。
那么开始设定接口和核心类。
所谓责任链模式的官方解释:
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
念起来有点拗口,但是其实很简单,我使用责任链模式,我希望达成的效果是:
改造前的业务逻辑图:
期望代码结构:
经过一段时间的改造,最终确定了下面的代码结构:
核心抽象类:
负责规范每一个处理流程的行为,包含:
3个抽象函数 handle 用于重写核心处理过程,endProcess 用于把处理结果反馈给外界,onProcess 流程正在执行,把进度反馈给外界。
此外,由于 处理流程是串联处理的,所以要提供一个非抽象方法 callNextHandler 调用下一个处理流程。
另外,观察下面的代码,无论是哪个处理函数,都会以 ResHandlerChain 作为其中一个参数。而 ResHandlerChain 就是所谓的 处理链条,用于容纳所有的处理流程对象,并且 提供一个函数,执行当前流程的核心handler函数。
import com.qiyuan.lib_offline_res_match.bean.OfflinePackages
/**
* 资源处理器抽象类
*/
abstract class ResHandler {
/**
* 处理过程
*/
abstract fun handle(chain: ResHandlerChain)
/**
* 结束流程,需要把结果反馈给外界
*/
abstract fun endProcess(chain: ResHandlerChain, resBool: Boolean)
/**
* 流程进行中,如果需要把进度反馈给外界的话
*/
abstract fun onProcess(chain: ResHandlerChain, progress: Int)
/**
* 构建新的链条,并且开始执行连锁任务
*/
fun callNextHandler(chain: ResHandlerChain) {
val newChain =
ResHandlerChain(
chain.handlerList,
chain.pkg,
chain.index + 1
)
newChain.proceed()
}
/**
* 处理器链条
*/
class ResHandlerChain(
val handlerList: List<ResHandler>,
val pkg: OfflinePackages,
val index: Int = 0
) {
fun proceed() {
// 取得当前index的handler对象并且执行
val currentHandler = handlerList[index]
currentHandler.handle(this)
}
}
}
复制代码
其实到了这里,核心类就设计完成。
下一步要做的就是给 实现具体的流程类:
首先是下载:
/**
* 资源下载处理器
*/
class ResDownloadHandler : ResHandler() {
override fun handle(chain: ResHandlerChain) {
val pkg = chain.pkg // 离线包对象
// 下载过程
DownloadUtil.get()?.download(
url = pkg.downloadPath,
saveDir = OfflinePkgSaveSpUtil.tempDir,
saveFileName = "${pkg.projectName}.zip",
listener = object : DownloadUtil.OnDownloadListener {
override fun onDownloadSuccess(path: String, contentType: String) {
pkg.localPath = path // 将下载后的地址保存到对象中传递下去
callNextHandler(chain)
}
override fun onDownloading(progress: Int) {
onProcess(chain, progress)
}
override fun onDownloadFailed(eStr: String) {
ResUpdateController.log("下载失败-${eStr}")
endProcess(chain, false)
}
})
}
override fun endProcess(chain: ResHandlerChain, resBool: Boolean) {
print("下载结束,结果为:$resBool ")
}
override fun onProcess(chain: ResHandlerChain, progress: Int) {
print("当前的下载进度是:$progress %")
}
}
复制代码
然后是:校验处理器
/**
* 校验处理器
*/
class ResMd5CheckHandler : ResHandler() {
override fun handle(chain: ResHandlerChain) {
checkMd5(chain)
}
/**
* 校验ZIP MD5值
*/
private fun checkMd5(chain: ResHandlerChain) {
val offlinePackages = chain.pkg
val path = offlinePackages.localPath
val targetMD5 = offlinePackages.fileMd5
val result = validateMd5(path, targetMD5)// 对比MD5值,看是否篡改过
if (result) {
callNextHandler(chain)
} else {
endProcess(chain, false)
}
}
/**
* 获取单个文件的MD5值!
*
* @param file
* @return
*/
private fun getFileMD5(file: File): String? {
//... 非核心代码,不展示了
}
/**
* 校验下载下来的文件的MD5值
*/
private fun validateMd5(oriFile: String, targetMd5: String): Boolean {
val m = getFileMD5(File(oriFile))
if (m == null) {
return false
}
return targetMd5.contains(m)
}
override fun endProcess(chain: ResHandlerChain, resBool: Boolean) {
print("校验结束,结果为:$resBool ")
}
override fun onProcess(chain: ResHandlerChain, progress: Int) {
}
}
复制代码
最后是解压处理器:
/**
* 解压控制器
*/
class ResUnzipHandler : ResHandler() {
private val zip = ".zip"
override fun handle(chain: ResHandlerChain) {
unzip(chain)
}
/**
* 解压
*/
private fun unzip(chain: ResHandlerChain) {
val pkg = chain.pkg
val path = pkg.localPath
val f = File(path)
ResUpdateController.log("开始解压:${f.name}")
val folder = "${OfflinePkgSaveSpUtil.tempDir}/${pkg.projectName}"
val success = doUnzip(path, folder, true)
ResUpdateController.log("p-> ${pkg.name}解压完毕,结果$success")
endProcess(chain, success)
}
@Throws(IOException::class)
private fun doUnzip(
archive: String,
decompressDir: String,
isDeleteZip: Boolean
): Boolean {
// 非核心代码,不展示了
}
override fun endProcess(chain: ResHandlerChain, resBool: Boolean) {
print("解压结束,结果为:$resBool ")
}
override fun onProcess(chain: ResHandlerChain, progress: Int) {
}
}
复制代码
处理类都写完了,最后一步就只剩下了调用责任链:
在我需要多线程去下载离线包的时候,构建处理流程的顺序,就是责任链的执行顺序:
private fun downloadMultiFiles(list: List<OfflinePackages>) {
list.forEach { packages ->
// 构建责任链,并且开始执行处理流程
ResHandler.ResHandlerChain(
listOf(
ResDownloadHandler(),// 下载处理器
ResMd5CheckHandler(),// 校验处理器
ResUnzipHandler() // 解压处理器
), packages
).proceed()
}
}
复制代码
至此,改造完成。
Result
对比一下改造前后的代码:
改造之前:上千行的代码量,维护困难,逻辑混乱,并且3个流程之间前后耦合,如果想新增流程,容易误伤其他代码。
改造之后:根据功能的不同,下载,校验和解压的代码分别处于3个类中,
与责任链的核心抽象类在一起,可以单独维护某一个流程而无需担心误伤其他流程。
并且,可扩展性也增强了,如果我需要在下载和校验之间再加入一个其他流程,我只需要在构建责任链对象的时候,加入一个新的流程即可。
// 构建责任链,并且开始执行处理流程
ResHandler.ResHandlerChain(
listOf(
ResDownloadHandler(),// 下载处理器
ResMd5CheckHandler(),// 校验处理器
ResDealHandler(),//TODO 再加一个流程...甚至可以再加N个流程
ResDeal2Handler(),//TODO 再加一个流程...甚至可以再加N个流程
ResDeal3Handler(),//TODO 再加一个流程...甚至可以再加N个流程
ResDeal4Handler(),//TODO 再加一个流程...甚至可以再加N个流程
ResUnzipHandler() // 解压处理器
), packages
).proceed()
复制代码