Three-day Thinking in Go (Day 2)
用户定义类型
类型组合
用户可以利用组合(composition)来从已有的类型中定义新的类型
1type (
2 car struct {
3 year int
4 maker string
5 }
6
7 ev struct {
8 car car
9 chargeRate float32
10 }
11)
12
13model3 := ev {
14 chargeRate, 10.0
15 car: car{
16 year: 2017,
17 maker: "Tesla",
18 }
19}
20// can visit
21model3.car.maker
22model3.chargeRate
匿名类型组合
相当于把nested的类型展开了
1type (
2 car struct {
3 year int
4 maker string
5 }
6
7 ev struct {
8 car
9 chargeRate float32
10 }
11)
12
13model3 := ev {
14 chargeRate, 10.0
15 car: car{ // 默认用type的名字
16 year: 2017,
17 maker: "Tesla",
18 }
19}
20// can visit
21model3.maker
22model3.chargeRat
并行类型
- 用户可以把一个类型定义为一个已有的类型
- 这个类型是全新的(并行的类型),并不是别名,
- 和原始type使用的时候可能需要type promotion
- 类型上的方法不会继承
匿名结构体
有时候,有一些用完即抛的操纵,比如un-marshall json,或者test case的输入, 可以考虑创建匿名结构体
1func main() {
2 model3 := struct {
3 year int
4 maker string
5 } {
6 year: 2017,
7 maker: "Tesla",
8 }
9}
方法
- 类型方法是一种特殊的function,定义的是type的行为
- 用来区分类型方法还是函数是, 函数接收器(function receiver), 有接收器的是方法, 没有接收器的为函数
- 可以用在primitive类型也可以用户定义的类型
- pass by value (这个value可以是指针)
- 用 dot 来访问 type上的方法
- 对于 mutable 和 immutable 的区分是在函数接收器的传递上
- 指针方法,指针作为接收器,
- 值方法,传值作为接收器,
1// 值方法 value semantic
2package main
3
4import "fmt"
5import "time"
6
7type car struct {
8 year int
9 maker string
10}
11
12func (c car) isNew() bool {
13 return c.year == time.Now().Year()
14}
15
16}
17func (c car) setYear() {
18 c.year = time.Now().Year()
19}
20
21func main() {
22 myCar := car{
23 year: 2019,
24 maker: "Tesla",
25 }
26 fmt.Printf("%#v\n", myCar) // => main.car{year:2019, maker:"Tesla"}
27 myCar.setYear()
28 fmt.Printf("%#v\n", myCar) // => main.car{year:2019, maker:"Tesla"}
29}
1// mutation的方法需要使用 指针方法 Value Semantic
2func (c *car) setYear() {
3 c.year = time.Now().Year()
4}
接口
An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.
- 接口声明行为
- 接口名字一般使用er/or结尾的表动作的名词
- 多态
- 接口不是类型的一部分
- 接口一个双字数据结构
- 一个指针指向type相关的信息结构体 iTable(type info + method set)
- 一个指针指向数据
- 不需要显式的声明某一个类型实现了某一个接口
- 通过方法集弄清楚一个类型是否实现了某接口
- 接口可以组合composite
- 尽可能让接口小 (The bigger the interface, the weaker the abstraction - Rob Pike)
- 小心接口污染
1package main
2
3import "fmt"
4
5type painter interface {
6 paint(color string)
7}
8
9type table struct {
10 brand, color string
11}
12
13func (t table) paint(color string) {
14 t.color = color
15}
16
17type chair struct {
18 brand, color string
19}
20
21func (c *chair) paint(color string) {
22 c.color = color
23}
24
25func paintIt(p painter, color string) {
26 p.paint(color)
27}
28
29func main() {
30 tasks := []painter{
31 table{brand: "s", color: "black"},
32 chair{brand: "c", color: "red"}, // => cannot use chair literal (type chair) as type painter in slice literal: chair does not implement painter (paint method has pointer receiver)
33 &chair{brand: "c", color: "red"}, // => 用空interface
34 }
35
36 for i := 0; i < len(tasks); i++ {
37 paintIt(tasks[i], "white")
38 fmt.Println(tasks[i])
39 }
40}
41
接口方法集和(Method Set)规则
- method set 记录了对于一个type支持的方法
- 对于一个类型T,method set为其receiver为T类型的方法
- 对于类型*T, method set则包括了receiver为T和*T类型的方法
接收器 | caller T | caller *T |
---|---|---|
值 | included | included |
指针 | no | included |
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Painter interface {
9 Paint()
10 RemovePaint()
11 Color()
12}
13
14type Tom struct{}
15
16func (t Tom) Color() {}
17func (t *Tom) Paint() {}
18func (t *Tom) RemovePaint() {}
19
20func main() {
21 var t Tom
22 elemType := reflect.TypeOf(&t).Elem() // Elem returns the value that the interface v contains or that the pointer v points to
23 n := elemType.NumMethod()
24 for i := 0; i < n; i++ {
25 fmt.Println(elemType.Method(i).Name)
26 } // => Color
27
28 var pt *Tom
29 elemType = reflect.TypeOf(&pt).Elem()
30 n = elemType.NumMethod()
31 for i := 0; i < n; i++ {
32 fmt.Println(elemType.Method(i).Name)
33 } // => Color, Paint, RemovePaint
34}
35
利用reflection可以根据call实现了同一个interface方法的不同类型
1package main
2
3import "fmt"
4
5type painter interface {
6 paint(color string)
7}
8
9type table struct {
10 brand, color string
11}
12
13func (t table) paint(color string) {
14 t.color = color
15}
16
17type chair struct {
18 brand, color string
19}
20
21type tv struct {
22 brand, color string
23}
24
25func (c *chair) paint(color string) {
26 c.color = color
27}
28
29func paintIt(o interface{}, color string) {
30 if p, ok := o.(painter); ok {
31 p.paint(color)
32 return
33 }
34 fmt.Println("no way to paint this one")
35}
36
37func main() {
38 tasks := []interface{}{
39 &table{brand: "table", color: "black"},
40 &chair{brand: "chair", color: "red"},
41 &tv{brand: "tv", color: "while"},
42 }
43
44 for i := 0; i < len(tasks); i++ {
45 fmt.Println("paint", tasks[i], "to white color")
46 paintIt(tasks[i], "white")
47 fmt.Println("after paint", tasks[i])
48 }
49}
50
51// paint &{table black} to white color
52// after paint &{table black}
53// paint &{chair red} to white color
54// after paint &{chair white}
55// paint &{tv while} to white color
56// no way to paint this one
57// after paint &{tv while}
Testing
- 测试是go的一等公民,所以自带了一个testing的框架
- test file 必须以
_test.go
结尾 - test 方法命名必须以
Test
开头 - test code 和 code 在同一个文件夹
- table testing 是 idiomatic, 标准库很多test是这么写的
- 自带test coverage tool
- 可以方便的测试http
1go test # 在当前folder跑test
2go test -v # 在当前folder跑test 并打印测试信息
3go test .../. # 在当前文件夹以及子文件夹中跑test
4go test --run hello # 跑含有hello的tests
5go test -coverprofile=cov.out # 生成测试覆盖结果
6go tool cover -html=cov.out # 在网页中测试覆盖结果
Assert/BDD/TDD/Mocking
1go get github.com/stretchr/testify
BDD: Ginkgo/goblin Mock: moq
Table-driven Test
1func sayNumber(i int) int {
2 return i*2
3}
4
5func TestSayNumber(t *testing.T) {
6 testcases := []map[string]struct{
7 value, expect int
8 } {
9 "test_one": {1, 10}
10 "test_two": {2, 20}
11 }
12
13 for _, u := range testcases {
14 actual, expected := sayNumber(u.value), u.expected
15 if actual != expected {
16 t.Fatalf("Use case %v expected %v but got %v", u.value, expected, actual)
17 }
18 } // => Failed Use case 1 exptected 10 but got 2
19}
MicroBenchmarks
- go自带有性能测试的支持
- Benchmark tests 也放在
_test.go
的文件中 Benchmark
为test的前缀- Test用的是
testing.T
而Benchmark用的是testing.B
- 测试跑的次数
b.N
会自动被调整 确保测量是可靠的 - 默认的测试时间是
1s
但是可以通过-benchtime
调整 - 可以用
-benchmeme
来检测内存的分配 - 跑test并不会跑Benchmark
- assign 返回值 避免 inlining
1var Output string
2
3func BenchmarkGreet(b *testing.B) {
4 // test setup
5
6 // reset timer if needed
7 b.ResetTimer()
8
9 var output string
10 for i := 0; i < b.N; i++ {
11 // assign return avoid inlining
12 output = greeter.Greet("hi")
13 }
14 Output = output
15}
16// go test -bench Greet
17// go test -bench Greet --benchmem
18// go test -bench Greet --benchtime 5s