Android进阶宝典 -- 深究23种设计模式(下)

748 阅读8分钟

Android进阶宝典 -- 深究23种设计模式(上)中,着重介绍了创建型设计模式和结构型设计模式,接下来我们介绍最后一种类型的设计模式 - 行为型模式。

何为行为型,其实在业务开发过程中,我们会经常碰到一些重复型的任务,我们不想这些任务都单独去做处理,更想模板化地进行统一处理,这个时候其实就会用到行为型设计模式中的一些模式。

1 模版方法模式

模版方法有点类似于门面设计模式,通常对于一些场景,我们可以抽象成多个环节,而具体环节的执行,可以交由子类去实现。

例如我们需要设计一个播放器组件,对于播放器来说,想要播放需要一些前置的流程任务的,例如:

interface IVideoPlayer {
    
    fun prepare()
    fun setDuration(duration:Long)
    fun setVideoInfo(videoInfo: VideoInfo)
    fun start()
    fun pause()
    fun release()
}

那么具体的实现就可以交给具体实现类完成,例如我们想要使用Google的ExoPlayer,那么就可以实现一个ExoVideoPlayer类来实现具体的内部逻辑。

class ExoVideoPlayer : IVideoPlayer {
    override fun prepare() {
        
    }

    override fun setDuration(duration: Long) {
        
    }

    override fun setVideoInfo(videoInfo: VideoInfo) {
        
    }

    override fun start() {
        
    }

    override fun pause() {
        
    }

    override fun release() {
        
    }
}

这种设计的思想优势在于,后续如果想要替换播放器的内核,要使用阿里云的播放器,那么就可以再创建一个阿里云播放器的实现类,调用阿里云播放器的API实现内部逻辑,不会违背开闭原则。

class AliVideoPlayer : IVideoPlayer {
    override fun prepare() {
        TODO("Not yet implemented")
    }

    override fun setDuration(duration: Long) {
        TODO("Not yet implemented")
    }

    override fun setVideoInfo(videoInfo: VideoInfo) {
        TODO("Not yet implemented")
    }

    override fun start() {
        TODO("Not yet implemented")
    }

    override fun pause() {
        TODO("Not yet implemented")
    }

    override fun release() {
        TODO("Not yet implemented")
    }
}

2 策略模式

使用策略模式,有点类似于我们之前提到的静态代理模式,拿上面的例子举例,我们在使用播放器的时候,可以设计一个策略类,通过业务上层来决定使用哪个播放器的内核。

class VideoPlayerContext:IVideoPlayer {

    private var videoPlayer:IVideoPlayer? = null
    
    fun setVideoPlayer(videoPlayer: IVideoPlayer){
        this.videoPlayer = videoPlayer
    }

    override fun prepare() {
        videoPlayer?.prepare()
    }

    override fun setDuration(duration: Long) {
        TODO("Not yet implemented")
    }

    override fun setVideoInfo(videoInfo: VideoInfo) {
        TODO("Not yet implemented")
    }

    override fun start() {
        TODO("Not yet implemented")
    }

    override fun pause() {
        TODO("Not yet implemented")
    }

    override fun release() {
        TODO("Not yet implemented")
    }


}

其实这种设计方式很多见,核心还是在于要实现调用层与实现层的解耦,而且对于业务方来说,只需要选择某个策略就可以实现灵活的切换。

3 命令模式

命令设计模式,这个在实际的开发中还是会经常使用到的,从字面意思上来看,就是通过调用方发起一个指令,然后接收方接收到这个指令之后,就会执行对应的操作,尤其是对于一些同类型的任务,我们可以聚合分类,做统一的处理。

例如我们现在有这样一个场景:我们都用过小度音响,或者小爱同学,他们能够帮助我们完成一些系统的指令,例如开关机、调整屏幕亮度、调节音量等,其实就是业务方发起一个指令之后,系统接收到这个指令后来完成对应的处理。

interface ICommand {
    fun execute(commandName:String)
}

这里我们定义了一个命令接口,其中execute方法就是用来做具体的指令执行。

class CommandInvoker {

    private var command:ICommand? = null
    fun setCommand(command:ICommand){
        this.command = command
    }
    fun call(commandName:String){
        command?.execute(commandName)
    }
}

CommandInvoker是调用方的逻辑,用于分发指令给到具体的实现方。

class SystemOsCommand(val receiver: SystemOsCommandReceiver) : ICommand {
    override fun execute(commandName: String) {
        receiver.onReceive(commandName)
    }
}

这里我们看到,SystemOsCommand实现了ICommand接口,但是并没有直接去处理指令,而是依赖了SystemOsCommandReceiver,最终交给SystemOsCommandReceiver来处理指令。

class SystemOsCommandReceiver {
    fun onReceive(commandName:String){

    }
}

可能很多伙伴们觉得,这不是多此一举吗,在SystemOsCommand中处理逻辑没问题。其实我一开始也是这么觉得,但是对于设计者来说,也是为了解耦,如果不考虑这些问题,那么在调用时就可以直接处理指令,那么逻辑全部强耦合在一起,不易于扩展。

所以针对这个问题,我对命令模式做了一次修改,既然在调用方调用之前就已经知道命令的类型了,其实就少了一步setCommand方法的调用。

class CommandDispatcher : ICommand {

    private var systemOsCommand: SystemOsCommand? = null

    override fun execute(commandType: CommandType, commandName: String) {
        when (commandType) {
            CommandType.SYSTEM_OS -> {
                if (systemOsCommand == null) {
                    systemOsCommand = SystemOsCommand(SystemOsCommandReceiver())
                }
                systemOsCommand?.execute(commandName)
            }
        }
    }
}
enum class CommandType(desc:String) {
    SYSTEM_OS("系统指令")
}

在使用命令模式的时候,其实会有大量的衍生类产生,因此决定是否使用命令模式,还是要区分具体的场景来处理。

4 责任链模式

责任链设计模式,通常在多个任务之间存在一定的关联关系,而且下一个任务,往往依赖上一个任务的状态决定是否执行。如果我们不采用责任链设计模式,那么就会存在一堆 if-else 判断逻辑,

val result = getIdResult()
if (result == STATUS.FAIL){
    //交给下个任务处理
    val nameResult = getNameResult()
    if (nameResult == STATUS.FAIL){
        //交给下一级处理
        val imageResult = getImageResult()
        if (imageResult == STATUS.FAIL){
            //交给下一级处理
        }else{
            Toast.makeText(context,"找到组件了",Toast.LENGTH_SHORT).show()
        }
    }else{
        Toast.makeText(context,"找到组件了",Toast.LENGTH_SHORT).show()
    }
}else{
    //成功
    Toast.makeText(context,"找到组件了",Toast.LENGTH_SHORT).show()
}

这种逻辑完全耦合在一起,而且在后人接手代码的时候,甚至很难修改里面的逻辑,很有可能一修改就带来其他的问题。

那么责任链设计模式就是用来解决这个问题,我们就拿上面的例子来说,有做过自动化测试的伙伴应该有了解的,一个组件的属性可能包括:id、name,如果在ViewTree中找不到这个组件,如果有组件的截图,可以通过图像算法找到对应的组件位置。

abstract class AbsNodeHandler {

    protected var mHandler: AbsNodeHandler? = null

    fun setNextHandler(handler: AbsNodeHandler) {
        this.mHandler = handler
    }
    
    abstract fun handleOperation(): STATUS
}

这里是有一个抽象的责任链处理类,其中每一个任务都可以作为一个责任链元素。

class NodeIdHandler : AbsNodeHandler() {
    override fun handleOperation(): STATUS {
        return if (getIdResult() == STATUS.FAIL) {
            mHandler?.handleOperation() ?: STATUS.FAIL
        } else {
            STATUS.OK
        }
    }

    //这里只是模拟,假设在这里处理得到了一个结果,但不一定是STATUS.OK
    private fun getIdResult(): STATUS {
        return STATUS.OK
    }
}
class NodeNameHandler : AbsNodeHandler() {
    override fun handleOperation(): STATUS {
        return if (getNameResult() == STATUS.FAIL) {
            mHandler?.handleOperation() ?: STATUS.FAIL
        } else {
            STATUS.OK
        }
    }

    private fun getNameResult(): STATUS {
        return STATUS.OK
    }
}
class NodeImageHandler : AbsNodeHandler() {
    override fun handleOperation(): STATUS {
        return getImageResult()
    }

    private fun getImageResult(): STATUS {
        return STATUS.OK
    }
}

这里列了3个任务,其中的逻辑就是,当一个任务执行失败之后,就直接交给下一个责任链节点mHandler来处理任务。

val nodeIdHandler = NodeIdHandler()
val nodeNameHandler = NodeNameHandler()
val modeImageHandler = NodeImageHandler()
nodeIdHandler.setNextHandler(nodeNameHandler)
nodeNameHandler.setNextHandler(modeImageHandler)
val result = nodeIdHandler.handleOperation()
if (result == STATUS.FAIL){
    Toast.makeText(context,"查找组件失败了",Toast.LENGTH_SHORT).show()
}else{
    //成功
    Toast.makeText(context,"找到组件了",Toast.LENGTH_SHORT).show()
}

这样逻辑看起来就非常清晰,而且在某个任务逻辑发生变化时,并不会影响到上面的逻辑调用处,而只需修改每个Handler内部逻辑即可。

5 观察者模式

观察者模式使用的场景就太多了,相信很多伙伴们也都知道这个设计模式,最常见的起始就是回调,注册一个onClickListener,就可以拿到点击事件的回调。

其实观察者模式主要有2个对象:观察者和被观察者,对于被观察者来说,会持有一个或者一组观察者,当有消息订阅发出时,会通知所有的观察者。

abstract class AbsObservable {
    
    private val observers:MutableList<AbsObserver> by lazy { 
        mutableListOf()
    }
    
    fun register(observer: AbsObserver){
        if (!observers.contains(observer)){
            observers.add(observer)
        }
    }
    
    fun unregister(observer: AbsObserver){
        if (observers.contains(observer)){
            observers.remove(observer)
        }
    }
    
    abstract fun notifyObservers()
}

具体的被观察者,可以实现notifyObservers方法,用于通知所有的观察者。

class RealObservable : AbsObservable() {
    override fun notifyObservers() {
        observers.forEach {
            it.receive()
        }
    }
}

对于观察者来说,只需要被动地接收被观察者传递过来的数据即可。

interface AbsObserver {
    fun receive()
}
class Observer1: AbsObserver {
    override fun receive() {
        
    }
}
class Observer2 : AbsObserver {
    override fun receive() {

    }
}

作为具体的观察者,Observer1和Observer2,当注册到RealObservable中之后,就可以接收到状态变化的通知,从而做出相应的处理。

val observer1 = Observer1()
val observer2 = Observer2()
val realObservable = RealObservable()
realObservable.register(observer1)
realObservable.register(observer2)
realObservable.notifyObservers()

因为现在使用观察者与被观察者的组件特别多,可能很少再去写这种框架,但是其中的原理我们多少还是要知道一点的。

6 访问者模式

如果做过ASM插桩的伙伴们,应该了解这个设计模式,可以对某个Method进行访问,然后获取这个方法中的信息,如果感兴趣的伙伴,可以看一下 Android字节码插桩全流程解析,但是在实际的开发中这种设计模式根本用不到。

其次还有备忘录模式、解释器模式等,其实也是一种思想,但是实际的开发中并没有太多的用处,其实23种设计模式,并不意味着所有的设计模式在日常开发中都可以用到,而是需要掌握其中的思想,在设计代码时,能够更加灵活,带一点“洁癖”。