在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种设计模式,并不意味着所有的设计模式在日常开发中都可以用到,而是需要掌握其中的思想,在设计代码时,能够更加灵活,带一点“洁癖”。