注:本文的灵感来源于GOPHER 2020年大会陈皓的分享,原PPT的链接 可能并不方便获取,所以我下载了一份PDF 到git仓,方便大家阅读。我将结合自己的实际项目经历,与大家一起细品这份文档。
目录
Slice Internal 关于Slice的实现,我之前有一讲 专门分析过底层实现。考虑到很多朋友没有细看,那我就再简单地讲一下。
1 2 3 4 5 type slice struct { array unsafe.Pointer len int cap int }
掌握Slice的底层实现,能让你真正理解一些看似“奇怪的”现象:
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { foo := make ([]int , 5 ) foo[3 ] = 42 foo[4 ] = 100 bar := foo[1 :4 ] bar[1 ] = 99 fmt.Println(foo) fmt.Println(bar) }
Tip: bar和foo是共享slice结构体底层的array的,所以修改了bar数组,foo也会变化
1 2 3 4 5 6 7 8 9 10 func main () { a := make ([]int , 32 ) b := a[1 :16 ] a = append (a, 1 ) a[2 ] = 42 fmt.Println(b) }
Tip: a和b原来是共享array的,但在a = append(a, 1)后发生了扩容,a和b指向的array发生了变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { path := []byte ("AAAA/BBBBBBBBB" ) sepIndex := bytes.IndexByte(path,'/' ) dir1 := path[:sepIndex] dir2 := path[sepIndex+1 :] fmt.Println(cap (dir1),cap (dir2)) fmt.Println("dir1 =>" ,string (dir1)) fmt.Println("dir2 =>" ,string (dir2)) dir1 = append (dir1,"suffix" ...) fmt.Println("dir1 =>" ,string (dir1)) fmt.Println("dir2 =>" ,string (dir2)) }
Tip: 核心点在于理解dir1和dir2的cap分别是14和9。由于dir1的当前len=4,append的长度=6,4+6<14,所以不会发生扩容
Deep Comparison 我们先看一下示例,data
结构体中四个注释为not comparable
表示无法直接用 == 符号对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 type data struct { num int checks [10 ]func () bool // not comparable doit func () bool // not comparable m map [string ]string bytes []byte } func main () { v1 := data{} v2 := data{} fmt.Println("v1 == v2:" , reflect.DeepEqual(v1, v2)) m1 := map [string ]string {"one" : "a" , "two" : "b" } m2 := map [string ]string {"two" : "b" , "one" : "a" } fmt.Println("m1 == m2:" , reflect.DeepEqual(m1, m2)) s1 := []int {1 , 2 , 3 } s2 := []int {1 , 2 , 3 } fmt.Println("s1 == s2:" , reflect.DeepEqual(s1, s2)) }
Tip: 示例比较复杂,其实要表达的内容比较简单:
函数、map、切片(不包括数组)以及它们的复合结构(如函数的数组),无法直接对比,只能用 reflect.DeepEqual
Function vs Receiver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Person struct { Name string Sexual string Age int } func PrintPerson (p *Person) { fmt.Printf("Name=%s, Sexual=%s, Age=%d\n" , p.Name, p.Sexual, p.Age) }func (p *Person) Print () { fmt.Printf("Name=%s, Sexual=%s, Age=%d\n" , p.Name, p.Sexual, p.Age) }func main () { var p = Person{ Name: "Hao Chen" , Sexual: "Male" , Age: 44 , } PrintPerson(&p) p.Print() }
Tip: 示例比较简单,但其中蕴含的意义非常大,如对Person这个对象的抽象、简化代码等。
另外值得一提的是,Go编译器会根据方法 func (p *Person) Print()
的定义,将 p.Print()
中的p从Person
转换为*Person
。
Interface Patterns 这个模块非常重要,希望大家倒一杯水,细细品尝。
示例是一个很简单的interface实现,用来打印接口,我们看看代码。
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 type Country struct { Name string } type City struct { Name string } type Printable interface { PrintStr() } func (c Country) PrintStr () { fmt.Println(c.Name) } func (c City) PrintStr () { fmt.Println(c.Name) } func main () { c1 := Country{"China" } c2 := City{"Beijing" } var cList = []Printable{c1, c2} for _, v := range cList { v.PrintStr() } }
那么,这时问题来了,如果我要实现N个Printable
,就要定义N个strcut+N个PrintStr()
方法。
前者的工作不能避免,而后者能否简化?那么示例来了
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 type WithName struct { Name string } type Country struct { WithName } type City struct { WithName } type Printable interface { PrintStr() } func (c WithName) PrintStr () { fmt.Println(c.Name) } func main () { c1 := Country{WithName{"China" }} c2 := City{WithName{"Beijing" }} var cList = []Printable{c1, c2} for _, v := range cList { v.PrintStr() } }
Tip: 核心就是用 embedded 的特性来删除冗余的代码。当然,代价是初始化会稍微麻烦点。
这时候,陈皓又给出了一个例子,即打印的内容会根据具体的实现不同时,无法直接用WithName
来实现
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 type Country struct { Name string } type City struct { Name string } type Printable interface { PrintStr() } func (c Country) PrintStr () { fmt.Println("Country:" , c.Name) } func (c City) PrintStr () { fmt.Println("City:" , c.Name) } func main () { c1 := Country{"China" } c2 := City{"Beijing" } var cList = []Printable{c1, c2} for _, v := range cList { v.PrintStr() } }
首先,我们要明确是否有必要优化。如果只有示例中这么几行代码,我们完全没必要改写。那如果系统真复杂到一定程度,我们该怎么办呢?
这是一个很发散性的问题,我这里给出一个个人比较常用的解决方案,作为参考。
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 type WithTypeName struct { Type string Name string } type Country struct { WithTypeName } func NewCountry (name string ) Printable { return Country{WithTypeName{"Country" , name}} } type City struct { WithTypeName } func NewCity (name string ) Printable { return City{WithTypeName{"City" , name}} } type Printable interface { PrintStr() } func (c WithTypeName) PrintStr () { fmt.Printf("%s:%s\n" , c.Type, c.Name) } func main () { c1 := NewCountry("China" ) c2 := NewCity("Beijing" ) var cList = []Printable{c1, c2} for _, v := range cList { v.PrintStr() } }
Tip: 这种方法的好处有很多(先不谈弊端),比如可以将具体的实现Country
和City
私有化,不对外暴露实现细节。今天不做细谈。
最后,送上一句经典:
Program to an interface, not an implementation
Github: https://github.com/Junedayday/code_reading
Blog: http://junes.tech/
Bilibili:https://space.bilibili.com/293775192
公众号:golangcoding