直接这么描述听起来比较抽象,下面我们结合UML类图详细看一下命令模式内部这几种基础组件的特性和具有的行为。
UML类图
命令模式的构成如下图所示
(资料图片)
请求的接收者Receiver我们做了简化,根据实际场景复杂度的需要我们也可以进一步抽象出接口和实现类,图中表示的命令模式一共由五种角色构成,下面详细解释下它们各自的特性和具有的行为
发送者是通常我们能接触到的终端,比如电视的遥控器,点击音量按钮发送加音量的命令,电视机里的芯片就是接收者负责完成音量添加的处理逻辑。
下面我们通过一个让PS5完成各种操作的例子,结合Golang代码实现理解一下用代码怎么实现命令模式。
代码示例
假设PS5的CPU支持A、B、C三个命令操作,
"本文使用的完整可运行源码去公众号「网管叨bi叨」发送【设计模式】即可领取"typeCPUstruct{}func(CPU)ADoSomething(){fmt.Println("adosomething")}func(CPU)BDoSomething(){fmt.Println("bdosomething")}typePS5struct{cpuCPU}func(pPS5)ACommand(){p.cpu.ADoSomething()}func(pPS5)BCommand(){p.cpu.ADoSomething()}funcmain(){cpu:=CPU{}ps5:=PS5{cpu}ps5.ACommand()ps5.BCommand()}
后续还可能会给CPU增加其他命令操作,以及需要支持命令宏(即命令组合操作)。如果每次都修改PS5的类定义,显然不符合面向对象开闭原则(Open close principle)的设计理念。
通过命令模式,我们把PS5抽象成命令发送者、CPU对象作为执行业务逻辑的命令接收者,然后引入引入Command 接口把两者做解耦,来满足开闭原则。
下面看一下用命令模式解耦后的代码实现,模式中各个角色的职责、实现思路等都在代码注释里做了标注,咱们直接看代码吧。
"本文使用的完整可运行源码去公众号「网管叨bi叨」发送【设计模式】即可领取"//命令接收者,负责逻辑的执行typeCPUstruct{}func(CPU)ADoSomething(paramint){fmt.Printf("adosomethingwithparam%v\n",param)}func(CPU)BDoSomething(param1string,param2int){fmt.Printf("bdosomethingwithparams%vand%v\n",param1,param2)}func(CPU)CDoSomething(){fmt.Println("cdosomethingwithnoparams")}//接口中仅声明一个执行命令的方法Execute()typeCommandinterface{Execute()}//命令对象持有一个指向接收者的引用,以及请求中的所有参数,typeACommandstruct{cpu*CPUparamint}//命令不会进行逻辑处理,调用Execute方法会将发送者的请求委派给接收者对象。func(aACommand)Execute(){a.cpu.ADoSomething(a.param)a.cpu.CDoSomething()//可以执行多个接收者的操作完成命令宏}funcNewACommand(cpu*CPU,paramint)Command{returnACommand{cpu,param}}"本文使用的完整可运行源码去公众号「网管叨bi叨」发送【设计模式】即可领取"typeBCommandstruct{statebool//Command里可以添加些状态用作逻辑判断cpu*CPUparam1stringparam2int}func(bBCommand)Execute(){ifb.state{return}b.cpu.BDoSomething(b.param1,b.param2)b.state=trueb.cpu.CDoSomething()}funcNewBCommand(cpu*CPU,param1string,param2int)Command{returnBCommand{false,cpu,param1,param2}}typePS5struct{commandsmap[string]Command}// SetCommand方法来将 Command 指令设定给PS5。func(p*PS5)SetCommand(namestring,commandCommand){p.commands[name]=command}//DoCommand方法选择要执行的命令func(p*PS5)DoCommand(namestring){p.commands[name].Execute()}funcmain(){cpu:=CPU{}// main方法充当客户端,创建并配置具体命令对象, 完成命令与执行操作的接收者的关联。ps5:=PS5{make(map[string]Command)}ps5.SetCommand("a",NewACommand(&cpu,1))ps5.SetCommand("b",NewBCommand(&cpu,"hello",2))ps5.DoCommand("a")ps5.DoCommand("b")}
本文的完整源码,已经同步收录到我整理的电子教程里啦,可向我的公众号「网管叨bi叨」发送关键字【设计模式】领取。
总结
关于命令模式的学习和实践应用,推荐有Java背景的同学看一下阿里开源的框架COLA 3.0,里面融合了不少DDD的概念,其中的Application层主要就是各种Command、Query对象封装了客户端的请求,它们的Execute方法负责将请求转发给Domain层进行处理从而完成业务逻辑。
最后我们再来总结一下命令模式的优缺点。
命令模式的优点
命令模式的缺点
扫码关注公众号「网管叨bi叨」
给网管个星标,第一时间吸我的知识
网管整理了一本《Go 开发参考书》收集了70多条开发实践。去公众号回复【gocookbook】领取!还有一本《k8s 入门实践》讲解了常用软件在K8s上的部署过程,公众号回复【k8s】即可领取!
觉得有用就点个在看
标签: