今天我们开始讲解设计模式中的行为模式,首先我们来看看责任链模式。
责任链模式就是把一系列的工作组成链路处理。其优势是对于调用者无需关注一项工作的实现细节,不需要自己去组装每一步的调用,同时对于如果有链路变更,只需要对链路的next进行修改,而无需对对象实现代码进行更改。
我们来看下一个实际场景,去医院看病的过程。我们需要经过挂号、就诊、医生开处方、缴费、取药这个过程,我们看看责任链模式是如何实现的?
type Process interface {
Exec() error
}
// 挂号
type Register struct {
next Process
}
// 就诊
type visitDoctor struct {
next Process
}
// 支付
type Pay struct {
next Process
}
// 取药
type pickMedicine struct {
next Process
}
func (r Register) Exec() error {
fmt.Println("挂号")
if r.next == nil {
return nil
}
return r.next.Exec()
}
func (v visitDoctor) Exec() error {
fmt.Println("就诊")
if v.next == nil {
return nil
}
return v.next.Exec()
}
func (p Pay) Exec() error {
fmt.Println("支付")
if p.next == nil {
return nil
}
return p.next.Exec()
}
func (p pickMedicine) Exec() error {
fmt.Println("取药")
if p.next == nil {
return nil
}
return p.next.Exec()
}
func NewProcess() Process {
return Register{
next: visitDoctor{
next: Pay{
next: pickMedicine{},
},
},
}
}
func main() {
NewProcess().Exec()
}
在上述例子中,用户只需要关注 Process 的 Exec()方法,而无需关注其内部调用流程,同时流程发生更改,也不会影响用户侧。
提到流程处理,大家有没有回忆起 go语言系列3 - Channel应用流水线模型 这一节的内容。个人认为通过流水线模型实现的正是责任链模式,而这种实现才是更体现了Go语言特色。
接下来我们再了解下行为模式的另一种命令模式。
命令模式解耦了触发事件和真正的执行,最常见的案例是在编辑器的Undo和Redo操作。其中UI层只需要触发点击按钮或者键盘响应的事件,而真正的逻辑处理交给逻辑层结合具体的对象来处理。
我们来简化编辑器的逻辑,只有插入字符或者删除字符操作。通过以下逻辑实现Undo和Redo操作:
存在一个undo队列和一个redo队列;
当插入/删除字符时,undo队列增加相应的操作事件,清空redo队列;
当执行undo操作时,取出undo队列中最后一个事件并执行,同时增加该事件对应的redo事件添加到redo队列中;
当执行redo操作时,取出redo队列中最后一个事件并执行,同时增加该事件对应的undo事件添加到undo队列中;
我们通过代码来实现上述过程:
type Command interface {
undo()
redo()
}
type InsertCommand struct {
content string
pos int
}
func (i InsertCommand) undo() {
fmt.Printf("undo: 在第 %d 位置删除 %s \n", i.pos, i.content)
}
func (i InsertCommand) redo() {
fmt.Printf("redo: 在第 %d 位置插入 %s \n", i.pos, i.content)
}
func OnInsert(content string, pos int) {
fmt.Printf("在第 %d 插入 %s \n", pos, content)
undos = append(undos, InsertCommand{
content: content,
pos: pos,
})
}
func OnUndo() {
if len(undos) == 0 {
return
}
command := undos[len(undos)-1]
command.undo()
undos = undos[:len(undos)-1]
redos = append(redos, command)
}
func OnRedo() {
if len(redos) == 0 {
return
}
command := redos[len(redos)-1]
command.redo()
redos = redos[:len(redos)-1]
undos = append(undos, command)
}
var (
undos []Command
redos []Command
)
func main() {
OnInsert("abc", 1)
OnInsert("ef", 4)
OnUndo()
OnUndo()
OnRedo()
}
这个简单过程的例子显得代码比较多,这也是命令模式的一个确定,代码阅读起来不是那么容易。这只是实现了插入字符串这一个操作的undo、redo事件(还并未实现完整逻辑,只是用输出语句代替),在真正的编辑器中存在删除字符串、插入/删除图片等一系列操作,代码将会比现在复杂得多。
同时通过命令模式实现的undo、redo需要队列来存储命令,会产生内存占用,避免内存占用过多,可限制undo、redo的次数(队列长度来实现)。这个例子中对undo、redo的操作并没有使用锁来保持同步性,在生产实际中需要注意。
近期回顾: