缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之
命令模式
命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求方发出请求要求执行一个操作;接收方收到请求,并执行操作。命令模式主要适用于以下应用场景。(1)现实语义中具备“命令”的操作(如命令菜单、Shell命令等)。(2)请求的调用者和接收者需要解耦,使得调用者和接收者不直接交互。(3)需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作。(4)需要支持命令宏(即命令组合操作)。(摘自 谭勇德 <<设计模式就该这样学>>)
场景
- 某线上学校, 提供在线美术课, 需要为小朋友提供在线画板
- 画板的基本功能有: 点选颜色, 画点, 画线, 撤销上一步等操作
- 码农王二狗接到Leader张阿蛋下达的开发任务, 加班加点, 很快把绘图功能做好了, 但是撤销功能不知道该咋实现, 于是只好请教Leader:
- 王二狗: 张哥, 这个撤销功能, 百思不得姐啊
- 张阿蛋: 我看看你咋弄的…我晕, 你的画板简直就是裸奔啊, 暴露那么多绘图函数
- 王二狗: 难道…可是不暴露函数, 上层咋个调用啊?
- 张阿蛋: 整个命令模式啊, 任何绘图操作都封装为一个带参数的命令
- 王二狗: 不明觉厉…还是不懂 -_-|||
- 张阿蛋: 画板内部整个堆栈, 绘图就是命令入栈, 撤销就是命令出栈, 刷新就是把栈里的命令遍历重做.
- 王二狗: Got it! 张哥, 强!
设计
- ICanvas: 定义画板接口, 供上层调用
- IDrawCommand: 定义绘图命令接口
- IGraphics: 定义绘图上下文接口
- tMockGraphics: 虚拟的绘图上下文, 实现IGraphics接口
- tMockCanvas: 虚拟画板类, 实现ICanvas接口
- tColorCmd: 切换颜色的绘图命令, 实现IDrawCommand接口
- tDotCmd: 画点的绘图命令, 实现IDrawCommand接口
- tLineCmd: 画线的绘图命令, 实现IDrawCommand接口
单元测试
command_pattern_test.go
package behavioral_patternsimport ("image/color""learning/gooop/behavioral_patterns/command""testing")func Test_CommandPattern(t *testing.T) {canvas := command.NewMockCanvas()canvas.Command(command.NewColorCmd(color.Black))canvas.Command(command.NewDotCmd(1, 1))canvas.Command(command.NewLineCmd(0, 0, 100, 100))canvas.Undo()canvas.Undo()}
测试输出
$ go test -v command_pattern_test.go=== RUN Test_CommandPatterntMockGraphics.CleartMockGraphics.Color, (0,0,0,65535)tMockGraphics.CleartMockGraphics.Color, (0,0,0,65535)tMockGraphics.DrawDot, (1,1)tMockGraphics.CleartMockGraphics.Color, (0,0,0,65535)tMockGraphics.DrawDot, (1,1)tMockGraphics.DrawLine, (0,0) -> (100,100)tMockGraphics.CleartMockGraphics.Color, (0,0,0,65535)tMockGraphics.DrawDot, (1,1)tMockGraphics.CleartMockGraphics.Color, (0,0,0,65535)--- PASS: Test_CommandPattern (0.00s)PASSok command-line-arguments 0.001s
ICanvas.go
定义画板接口, 供上层调用
package commandtype ICanvas interface {Command(cmd IDrawCommand)Undo()}
IDrawCommand.go
定义绘图命令接口
package commandtype IDrawCommand interface {Draw(g IGraphics)}
IGraphics.go
定义绘图上下文接口
package commandimport "image/color"type IGraphics interface {Color(color color.Color)Clear()DrawDot(x int, y int)DrawLine(x0 int, y0 int, x1 int, y1 int)}
tMockGraphics.go
虚拟的绘图上下文, 实现IGraphics接口
package commandimport ("fmt""image/color")type tMockGraphics struct {color color.Color}func newMockGraphics() IGraphics {return &tMockGraphics{}}func (me *tMockGraphics) Clear() {fmt.Printf("\ntMockGraphics.Clear\n")}func (me *tMockGraphics) Color(color color.Color) {me.color = colorr,g,b,a := color.RGBA()fmt.Printf("tMockGraphics.Color, (%v,%v,%v,%v)\n", r, g, b, a)}func (me *tMockGraphics) DrawDot(x int, y int) {fmt.Printf("tMockGraphics.DrawDot, (%v,%v)\n", x, y)}func (me *tMockGraphics) DrawLine(x0 int, y0 int, x1 int , y1 int) {fmt.Printf("tMockGraphics.DrawLine, (%v,%v) -> (%v,%v)\n", x0, y0, x1, y1)}
tMockCanvas.go
虚拟画板类, 实现ICanvas接口
package commandimport "errors"type tMockCanvas struct {commands []IDrawCommandgraphics IGraphics}func NewMockCanvas() ICanvas {return &tMockCanvas{commands: make([]IDrawCommand, 0),graphics: newMockGraphics(),}}func (me *tMockCanvas) Command(cmd IDrawCommand) {me.push(cmd)me.update()}func (me *tMockCanvas) Undo() {e,_ := me.pop()if e != nil {return}me.update()}func (me *tMockCanvas) update() {me.graphics.Clear()for _,it := range me.commands {it.Draw(me.graphics)}}func (me *tMockCanvas) push(cmd IDrawCommand) {me.commands = append(me.commands, cmd)}func (me *tMockCanvas) pop() (error, IDrawCommand) {size := len(me.commands)if size <= 0 {return errors.New("no more commands"), nil}it := me.commands[size - 1]me.commands = me.commands[:size - 1]return nil,it}
tColorCmd.go
切换颜色的绘图命令, 实现IDrawCommand接口
package commandimport "image/color"type tColorCmd struct {color color.Color}func NewColorCmd(c color.Color) IDrawCommand {return &tColorCmd{c,}}func (me *tColorCmd) Draw(g IGraphics) {g.Color(me.color)}
tDotCmd.go
画点的绘图命令, 实现IDrawCommand接口
package commandtype tDotCmd struct {x inty int}func NewDotCmd(x int, y int) IDrawCommand {return &tDotCmd{x, y,}}func (me *tDotCmd) Draw(g IGraphics) {g.DrawDot(me.x, me.y)}
tLineCmd.go
画线的绘图命令, 实现IDrawCommand接口
package commandtype tLineCmd struct {x0 inty0 intx1 inty1 int}func NewLineCmd(x0 int, y0 int, x1 int, y1 int) IDrawCommand {return &tLineCmd{x0, y0, x1, y1,}}func (me *tLineCmd) Draw(g IGraphics) {g.DrawLine(me.x0, me.y0, me.x1, me.y1)}
命令模式小结
命令模式的优点(1)通过引入中间件(抽象接口),解耦了命令请求与实现。(2)扩展性良好,可以很容易地增加新命令。(3)支持组合命令,支持命令队列。(4)可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。命令模式的缺点(1)具体命令类可能过多。(2)命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。(摘自 谭勇德 <<设计模式就该这样学>>)
