关于golang:手撸golang-行为型设计模式-命令模式

4次阅读

共计 4258 个字符,预计需要花费 11 分钟才能阅读完成。

手撸 golang 行为型设计模式 命令模式

缘起

最近温习设计模式
拜读谭勇德的 << 设计模式就该这样学 >>
本系列笔记拟采纳 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_patterns

import (
    "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_CommandPattern

tMockGraphics.Clear
tMockGraphics.Color, (0,0,0,65535)

tMockGraphics.Clear
tMockGraphics.Color, (0,0,0,65535)
tMockGraphics.DrawDot, (1,1)

tMockGraphics.Clear
tMockGraphics.Color, (0,0,0,65535)
tMockGraphics.DrawDot, (1,1)
tMockGraphics.DrawLine, (0,0) -> (100,100)

tMockGraphics.Clear
tMockGraphics.Color, (0,0,0,65535)
tMockGraphics.DrawDot, (1,1)

tMockGraphics.Clear
tMockGraphics.Color, (0,0,0,65535)
--- PASS: Test_CommandPattern (0.00s)
PASS
ok      command-line-arguments  0.001s

ICanvas.go

定义画板接口, 供下层调用

package command

type ICanvas interface {Command(cmd IDrawCommand)
    Undo()}

IDrawCommand.go

定义绘图命令接口

package command

type IDrawCommand interface {Draw(g IGraphics)
}

IGraphics.go

定义绘图上下文接口

package command

import "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 command

import (
    "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 = color
    r,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 command

import "errors"

type tMockCanvas struct {commands []IDrawCommand
    graphics 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 command

import "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 command

type tDotCmd struct {
    x int
    y 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 command


type tLineCmd struct {
    x0 int
    y0 int
    x1 int
    y1 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)命令模式的后果其实就是接管方的执行后果,然而为了以命令的模式进行架构、解耦申请与实现,引入了额定类型构造(引入了申请方与形象命令接口),减少了了解上的艰难。(摘自 谭勇德 << 设计模式就该这样学 >>)

(end)

正文完
 0