携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情
前言
命令模式将任务请求封装成命令对象,命令对象中封装有任务处理者对象,调用者持有这个命令对象,这就使得调用者和处理者没有直接联系,降低了耦合度。同时,根据不同的请求可以参数化命令对象,可以将多个对象放入到集合中排队执行。
命令模式的UML图如下:
命名模式的通用结构:
- 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其实是一个策略模式。
关于工厂模式,策略模式,命令模式,这三者有相似也有不同,很容易混淆,后期有机会单独讲讲。
大家也不要死板应用这些设计模式,你不讲我还会写代码,你说了设计模式我还不会写了,不要被条条框框限制了。应理解之后灵活应用。