内容大纲
情节分析
例子与代码均来自《大话设计模式》程杰,简单记录加深印象。
举一个生活中的场景(请发挥想象力):烧烤摊 vs 烧烤店
混乱不堪的烧烤摊
“因为排队要吃烤肉的人太多了,都希望能最快的吃的烤肉,烤肉老板一个人,所以场面有些混乱”(请求排队)
“老板一个人,来的人一多,他也就未必记得住谁付没付过钱,要几串,要不要🌶等待”(未记录请求日志)
“客人等待不耐烦,需要退款!客人发现不熟请求重新烤”(撤销与重做)
“大家都在那里等着,没什么事,于是都盯着烤肉去了,哪一串多,哪一串少,哪一串烤得好,哪一串烤得焦都看得清清楚楚,于是挑剔就接踵而至”
编程中将此现象称为:紧耦合:“行为请求者(客人)” 与 “行为实现者(烧烤老板)”的紧耦合
结论:对请求排队或记录请求日志,以及支持可撤销的操作等行为时,‘行为请求者’ 与 ‘行为实现者’的紧耦合是不合适的。
有规矩的烤肉店
如何解耦? 开家门店!招服务员!利用一个服务员来解耦客户和烤肉师傅
客人无需看着如何烤肉的实现,只需要告诉服务员要什么(下订单),服务员记录,然后通知烤肉师傅去做。
因为每个客人都有订单(即记录),所有更改,撤销行为的操作都不会导致最终算错账!
代码设计部分
紧耦合设计:(烧烤摊)
学习用Kotlin 语言实现:
/**
* @Create on 2020/4/16 22:20
* @description 烧烤师傅类
* @author Mrdonkey
*/
class Barbecuer {
//烤羊肉
fun BakeMutton() {
println("烤羊肉串!")
}
fun BakeChickenWing() {
println("烤鸡翅!")
}
}
/**
* @Create on 2020/4/16 22:27
* @description 客户端(顾客)
* @author Mrdonkey
*/
class Client {
companion object {
@JvmStatic
fun main(vararg args: String) {
val boy = Barbecuer()
boy.BakeMutton()
boy.BakeChickenWing()
boy.BakeMutton()
boy.BakeChickenWing()
}
}
}
输出结果:
烤羊肉串!
烤鸡翅!
烤羊肉串!
烤鸡翅!
松耦合设计:(烧烤店)
Barbecue类用上面的
Command:
/**
* @Create on 2020/4/18 16:15
* @description 抽象命令
* @author mrdonkey
*/
abstract class Command constructor(val receiver: Barbecuer) {
//执行命令
abstract fun executeCommand()
}
具体的Command
/**
* @Create on 2020/4/18 16:23
* @description 具体的烤鸡翅命令,通知烤肉者
* @author mrdonkey
*/
class BakeChickenWingCommand constructor(receiver: Barbecuer) : Command(receiver) {
override fun executeCommand() {
receiver.bakeChickenWing()
}
}
/**
* @Create on 2020/4/18 16:20
* @description 具体的烤羊肉串命令,通知烤肉者
* @author mrdonkey
*/
class BakeMuttonCommand constructor(receiver: Barbecuer) : Command(receiver) {
override fun executeCommand() {
receiver.bakeMutton()
}
}
服务员类
/**
* @Create on 2020/4/18 16:24
* @description 服务员
* @author mrdonkey
*/
class Waiter {
//可以接受多个命令
private val orders = arrayListOf<Command>()
/**
* 设置订单 set the given[order]
*/
fun setOrder(vararg orders: Command) {
orders.forEach { cmd ->
when (cmd) {
is BakeChickenWingCommand -> {
println("服务员:鸡翅没有了,请点别的烧烤")
}
else -> {
this.orders.add(cmd)
println("增加订单:${cmd::class.java.simpleName} 时间:${currentTime()}")
}
}
}
}
/**
* 取消订单 cancel the given[order]
*/
fun cancelOrder(order: Command): Boolean {
orders.remove(order)
println("取消订单:${order::class.java.simpleName} 时间:${currentTime()}")
return false
}
/**
* 执行通知操作
*/
fun doNotify() {
orders.forEach { cmd ->
cmd.executeCommand()
}
}
/**
* 获取当前时间
*/
private fun currentTime() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(Date())
}
客户端测试
/**
* @create on 2020/4/18 17:14
* @description 客户端
* @author mrdonkey
*/
class Client {
companion object {
@JvmStatic
fun main(vararg args: String) {
//开店前准备
val boy = Barbecuer()
val bakeChickenWingCommand = BakeChickenWingCommand(boy)
val bakeMuttonCommand = BakeMuttonCommand(boy)
val girl = Waiter()
//开门准备 顾客点菜
girl.setOrder(bakeChickenWingCommand, bakeMuttonCommand,bakeMuttonCommand)
//点菜完毕,通知厨房
girl.doNotify()
}
}
}
测试结果:
服务员:鸡翅没有了,请点别的烧烤
增加订单:BakeMuttonCommand 时间:2020-04-18 17:29:44.236
增加订单:BakeMuttonCommand 时间:2020-04-18 17:29:44.238
师傅在烤羊肉串!
师傅在烤羊肉串!
命令模式
命令模式:将一个请求封装为一个对象,从而使你可用不同请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
直接放代码
Command类
/**
* @create on 2020/4/18 17:37
* @description Command类用来声明执行操作的接口
* @author mrdonkey
*/
abstract class Command constructor(val receiver: Receiver) {
/**
* 执行操作
*/
abstract fun execute()
}
ConcreateCommand类
/**
* @create on 2020/4/18 17:41
* @description 具体的Command 将一个接收者对象绑定于一个动作,调用该接受者相应的操作
* @author mrdonkey
*/
class ConcreteCommand constructor(receiver: Receiver) : Command(receiver) {
override fun execute() {
receiver.action()
}
}
Receiver类
/**
* @create on 2020/4/18 17:38
* @description Receiver类指定如何实施与执行一个请求相关的操作。任何一个类都可能成为一个接收者
* @author mrdonkey
*/
class Receiver {
/**
* 接收者相应的操作
*/
fun action() {
println("执行请求")
}
}
Invoker类
/**
* @create on 2020/4/18 17:43
* @description Invoker类 要求该Command执行这个请求
* @author mrdonkey
*/
class Invoker {
private var command: Command? = null
fun setCommand(command: Command):Invoker{
this.command = command
return this
}
fun executeCommand() {
command?.execute()
}
}
Client类
/**
* @create on 2020/4/18 17:45
* @description Cilent 客户端
* @author mrdonkey
*/
class Client {
companion object {
@JvmStatic
fun main(vararg arg: String) {
val receiver = Receiver()
val cmd = ConcreteCommand(receiver)
Invoker().setCommand(cmd)
.executeCommand()
}
}
}
总结
命令模式的优点
- 能较容易地设计一个命令队列
- 在需要的情况下,可以较容易地将命令记入日志
- 允许接收请求的一方决定是否要否决请求
- 可以容易地实现对请求的撤销与重做
- 加进新的具体的命令类不影响其他的类,因此增加新的具体命令类很容易
- 最关键的优点:命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开
命令模式使用场景
敏捷开发原则告诉我们,不要为代码添加基于猜测的,实际不需要的功能。
如果不清楚一个系统是否需要命令模式,一般不急于去实现它,事实上,在需要的时候通过重构实现这个模式并不难
只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义