注:本文的灵感来源于GOPHER 2020年大会陈皓的分享,原PPT的链接 可能并不方便获取,所以我下载了一份PDF 到git仓,方便大家阅读。我将结合自己的实际项目经历,与大家一起细品这份文档。
目录
Embedded 接口定义
1 2 3 4 5 6 7 8 type Painter interface { Paint() } type Clicker interface { Click() }
Label 实现了 Painter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Widget struct { X, Y int } type Label struct { Widget Text string } func (label Label) Paint () { fmt.Printf("%p:Label.Paint(%q)\n" , &label, label.Text) }
ListBox实现了Painter和Clicker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type ListBox struct { Widget Texts []string Index int } func (listBox ListBox) Paint () { fmt.Printf("ListBox.Paint(%q)\n" , listBox.Texts) } func (listBox ListBox) Click () { fmt.Printf("ListBox.Click(%q)\n" , listBox.Texts) }
Button也实现了Painter和Clicker
1 2 3 4 5 6 7 8 9 10 11 12 13 type Button struct { Label } func (button Button) Paint () { fmt.Printf("Button.Paint(%s)\n" , button.Text) } func (button Button) Click () { fmt.Printf("Button.Click(%s)\n" , button.Text) }
方法调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { label := Label{Widget{10 , 70 }, "Label" } button1 := Button{Label{Widget{10 , 70 }, "OK" }} button2 := Button{Label{Widget{50 , 70 }, "Cancel" }} listBox := ListBox{Widget{10 , 40 }, []string {"AL" , "AK" , "AZ" , "AR" }, 0 } for _, painter := range []Painter{label, listBox, button1, button2} { painter.Paint() } for _, widget := range []interface {}{label, listBox, button1, button2} { widget.(Painter).Paint() if clicker, ok := widget.(Clicker); ok { clicker.Click() } } }
这个例子代码很多,我个人认为重点可以归纳为一句话:
用嵌入实现方法的继承,减少代码的冗余度
耗子叔的例子很精彩,不过我个人不太喜欢interface
这个数据类型(main函数中),有没有什么优化的空间呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 type PaintClicker interface { Painter Clicker } func main () { for _, widget := range []PaintClicker{listBox, button1, button2} { widget.Paint() widget.Click() } }
IoC 先看一个Int集合的最基本实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type IntSet struct { data map [int ]bool } func NewIntSet () IntSet { return IntSet{make (map [int ]bool )} } func (set *IntSet) Add (x int ) { set.data[x] = true }func (set *IntSet) Delete (x int ) { delete (set.data, x) }func (set *IntSet) Contains (x int ) bool { return set.data[x] }
现在,需求来了,我们希望对这个Int集合的操作是可撤销的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 type UndoableIntSet struct { IntSet functions []func () } func NewUndoableIntSet () UndoableIntSet { return UndoableIntSet{NewIntSet(), nil } } func (set *UndoableIntSet) Add (x int ) { if !set.Contains(x) { set.data[x] = true set.functions = append (set.functions, func () { set.Delete(x) }) } else { set.functions = append (set.functions, nil ) } } func (set *UndoableIntSet) Delete (x int ) { if set.Contains(x) { delete (set.data, x) set.functions = append (set.functions, func () { set.Add(x) }) } else { set.functions = append (set.functions, nil ) } } func (set *UndoableIntSet) Undo () error { if len (set.functions) == 0 { return errors.New("No functions to undo" ) } index := len (set.functions) - 1 if function := set.functions[index]; function != nil { function() set.functions[index] = nil } set.functions = set.functions[:index] return nil }
上面的实现是一种顺序逻辑的思路,整体还是挺麻烦的。有没有优化思路呢?
定义一下Undo这个结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Undo []func () func (undo *Undo) Add (function func () ) { *undo = append (*undo, function) } func (undo *Undo) Undo () error { functions := *undo if len (functions) == 0 { return errors.New("No functions to undo" ) } index := len (functions) - 1 if function := functions[index]; function != nil { function() functions[index] = nil } *undo = functions[:index] return nil }
细品一下这里的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type IntSet2 struct { data map [int ]bool undo Undo } func NewIntSet2 () IntSet2 { return IntSet2{data: make (map [int ]bool )} } func (set *IntSet2) Undo () error { return set.undo.Undo() } func (set *IntSet2) Contains (x int ) bool { return set.data[x] } func (set *IntSet2) Add (x int ) { if !set.Contains(x) { set.data[x] = true set.undo.Add(func () { set.Delete(x) }) } else { set.undo.Add(nil ) } } func (set *IntSet2) Delete (x int ) { if set.Contains(x) { delete (set.data, x) set.undo.Add(func () { set.Add(x) }) } else { set.undo.Add(nil ) } }
我们看一下,这块代码的前后逻辑有了啥变化:
之前,撤销函数是在Add/Delete时添加的,函数中包含了IntSet的操作,也就是 Undo依赖IntSet
而修改之后,撤销函数被抽象为Undo,撤销相关的工作直接调用Undo相关的工作即可,也就是 IntSet依赖Undo
我们再来分析一下
Undo是控制逻辑 - 撤销动作
IntSet是业务逻辑 - 保存数据的功能。
业务逻辑依赖控制逻辑,才能保证在复杂业务逻辑变化场景下,代码更健壮!
Github: https://github.com/Junedayday/code_reading
Blog: http://junes.tech/
Bilibili:https://space.bilibili.com/293775192
公众号:golangcoding