Android常用设计模式-命令模式

1,217 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情

前言

命令模式将任务请求封装成命令对象,命令对象中封装有任务处理者对象,调用者持有这个命令对象,这就使得调用者和处理者没有直接联系,降低了耦合度。同时,根据不同的请求可以参数化命令对象,可以将多个对象放入到集合中排队执行。

命令模式的UML图如下:

image.png

命名模式的通用结构:

  • Reicever 接收者角色,负责具体执行一个请求,任何一个类都可以成为一个接收者,而在接收者类中封装具体操作逻辑的方法我们称为行动方法(action)
  • Command 命令角色 定义所有具体命令类的抽象接口
  • ConcreteCommand 具体命令角色,实现了Command接口,在excute方法中调用接收者角色的相关方法,在接收者和命令执行的具体行为之间加以弱耦合。execute方法成为执行方法(excute)
  • Invoker 调用者角色,负责调用命令对象执行具体的请求,相关的方法我们成为行动方法(action)
  • Client 客户端角色

接收者:

/**
 * 接收者,就是具体干活的人
 */
class Receiver {

    //这里执行的方法可以自己写,也可以通过接口定义,如果是多个接收者的话推荐接口定义
    fun doSth(){
        YYLogUtils.w("干点什么呢?搬个砖吧!")
    }

}

抽象命令

/**
 * 抽象的命令
 */
abstract class Command {
    abstract fun execute()
}

抽象命令的实现,具体的命令

/**
 * 具体的命令-去干什么事情
 */
class ConcreteCommand(private val receiver: Receiver) : Command() {
    //我们需要一个具体干活的人,就是Receiver

    override fun execute() {
        receiver.doSth()
    }

}

调用者的实现

/**
 * 调用者
 */
class Invoker(private val command: Command) {

    //调用者发号司令
    fun action(){
        command.execute()
    }

}

使用:

    fun commandTest() {
        val receiver = Receiver()
        val command = ConcreteCommand(receiver)
        val invoker = Invoker(command)
        invoker.action()
    }

单一接收者的命令

我们以一个场景为例子,比如一个虚拟遥控器,按上下左右去控制电视,那么我们的接收者就可以定义为电视,就是单个接收者

只需要一个接收者,我们定义不同的执行方式

class Television {

    fun leftAction() {
        YYLogUtils.w("电视上的往左选中")
    }

    fun rightAction() {
        YYLogUtils.w("电视上的往右选中")
    }

    fun upAction() {
        YYLogUtils.w("电视上的往上选中")
    }

    fun downAction() {
        YYLogUtils.w("电视上的往下选中")
    }
}

然后我们还是需要实现不同的具体命令。

class LeftCommand(private val tv: Television) : Command() {

    override fun execute() {
        tv.leftAction()
    }

}

class RightCommand(private val tv: Television) : Command() {

    override fun execute() {
        tv.rightAction()
    }

}

class UpCommand(private val tv: Television) : Command() {

    override fun execute() {
        tv.upAction()
    }

}

class DwonCommand(private val tv: Television) : Command() {

    override fun execute() {
        tv.downAction()
    }

}

Invoker 的调用者我们可以不需要变动,下面看看 Client 如何实现

    val television = Television()
    val leftCommand = LeftCommand(television)
    val rightCommand = RightCommand(television)
    val upCommand = UpCommand(television)
    val downCommand = DwonCommand(television)

    //传入不同的命令就可以实现不同的逻辑
    Invoker(leftCommand).action()
    Invoker(rightCommand).action()
    Invoker(upCommand).action()
    Invoker(downCommand).action()

多个接收者的命令

我们以一个场景为例子,比如校长发号施令,让老师去上课,语文老师上语文课,英语老师上英语课。这样就有多个接收者。

我们上面定义的 Invoker Command 都不需要改动,我们只需要改变具体的命令和具体执行命令的人。

不管是单个还是多个,我们把命令的人创建出来,定义具体的执行的方法

先定义接收者,也就是具体执行命令的人。

class ChineseTeacher {

    fun teach() {
        YYLogUtils.w("教学中文")
    }

}

class EnglishTeacher {

    fun teach() {
        YYLogUtils.w("教学英文")
    }

}

然后实现具体的命令,让具体的人去做具体的一些事情。这里要说明清除。

class TeachChineseCommand(private val teacher: ChineseTeacher) : Command() {

    override fun execute() {
        teacher.teach()
    }
}

class TeachEnglishCommand(private val teacher: EnglishTeacher) : Command() {

    override fun execute() {
        teacher.teach()
    }
}

使用的时候,还是可以用抽象的方法。

    val chineseTeacher = ChineseTeacher()
    val englishTeacher = EnglishTeacher()

    val chineseCommand = TeachChineseCommand(chineseTeacher)
    val englishCommand = TeachEnglishCommand(englishTeacher)

    Invoker(chineseCommand).action()
    Invoker(englishCommand).action()

所以命令模式的核心就是命令 Command 的具体实现,和 Receiver 具体执行的逻辑。

总结

例如Java的线程池的使用就是用到了命令模式,线程池对象可以执行execute方法,execute方法参数就是一个Runnable指令,此时就可以创建一个Runnable命令对象。

我们定义一个自己的Runnable,让通过线程池执行,去触发我们定义好的逻辑。

Executor executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
    @Override
    public void run() {
        LogUtil.d("do yourself in Runnable");
    }
});
Thread thread = new Thread(){
    @Override
    public void run() {
        super.run();
        LogUtil.d("do yourself in Thread");
    }
};
executor.execute(thread);

命令模式的优点与缺点

优点:

  • 解耦 调用者和接收者之间没有任何依赖或者引用关系。调用者只需要通过相应的具体命令就可以得出最后的结果,而不必了解具体是哪个接收者做的操作。
  • 可扩展性 Command的子类可以很容易扩展,调用者与高层模块也没有很强的耦合。

缺点:

  • 当命令比较多的时候会显得比较臃肿。

PS:完整的命令模式感觉就是普通工厂模式+策略模式的一个整合,Receiver到Command是一个工厂模式,而ConcreteCommand到Invoker其实是一个策略模式。

关于工厂模式,策略模式,命令模式,这三者有相似也有不同,很容易混淆,后期有机会单独讲讲。

大家也不要死板应用这些设计模式,你不讲我还会写代码,你说了设计模式我还不会写了,不要被条条框框限制了。应理解之后灵活应用。