Parts of Effective Go
方法
指针和值 Pointers vs Values
可以为任何已命名的类型(除了指针和接口)定义方法
接收者可以不是结构体
1 | type ByteSlice []byte |
1 | // 如果需要该方法可更新接收者 |
1 | // 为类型*ByteSlice 构建与标准Write方法 |
以指针或值为接收者的区别在于:
- 指针方法 可以修改接收者
- 值方法 会导致方法接收到的是该值的副本,任何修改都无意义
如果该值是可寻址的,当使用值调用指针方法时,Go
语言会自动插入取址符
编译器会将b.Write
重写为(&b).Write
在字节切片上使用io.Writer
接口已被bytes.Buffer
实现
接口和其它类型 Interfaces and other types
接口
Go
中的接口为指定的对象的行为提供了一种方法If something can do this, then it can be used here.
- 通过实现
String
方法,从而实现fmt.Stringer
接口,可以自定义打印函数 - 通过实现
Write
方法,从而实现io.Writer
接口,可以被fmt.Fprintf
写入
在Go
中,仅包含一两种方法的接口很常见,且其名称通常来自于实现它的方法Such as io.Writer for something that implements Write.
每种类型都能实现多个接口
例如实现了sort.Interface
接口的集合就可通过sort
包中的函数进行排序,该接口包括Len()
、Less(i, j int) bool
、Swap(i, j int)
方法,且该集合仍然可以实现fmt.Stringer
接口
1 | type Sequence []int |
类型转换 Conversions
Sequence
的String
方法重新实现了切片的String
方法
若在调用fmt.Sprint
之前将Sequence
转换为纯粹的[]int
,就能使用切片默认实现的String
方法
如果不进行类型转换的话,fmt.Sprint
会执行Sequence.String
方法,Sequence.String
方法又会执行fmt.Sprint
,溢栈。
1 | // 通过类型转换实现在String方法中安全调用Sprint |
上述类型转换过程并不会创建新值,它只是让现有的值看起来有个新类型而已
而有些合法的转换会创建新值,例如从整数转换为浮点数等
在Go
中,为了访问不同的方法集合,而进行类型转换的情况是非常常见的
1 | // 使用 sort.intSilce 来简化 |
不必让Sequence
实现多个接口(排序和打印),可以通过将数据转换为多种类型(Sequence
、sort.IntSlice
和[]int
)来使用相应的功能,这种方法往往很有效。
接口转换
类型选择(Type switch
)是类型转换的一种形式,它接受一个接口,在switch
中依据类型选择对应的case
,并在某种意义上将其转换为该种类型
1 | // fmt.Printf 类型选择简化版 |
当明确知道一个值的类型,使用类型断言就可以提取它
当类型选择只有一种情况(只会进入某个case
)时,使用类型断言就可以了
类型断言只接受一个接口值,并从中提取明确类型的值
格式value.(typeName)
提取字符串str := value.(string)
,如果它所转换的值中不包含字符串,该语句就会以运行时错误崩溃
可使用comma, ok
语句安全的判断该值是否是字符串
1 | // 若类型断言失败 |
1 | if str, ok := value.(string); ok { |
若某种现有的类型仅仅实现了一个接口,且除此之外并没有可导出的方法,则该类型本身就无需导出
仅导出该接口能让调用者更专注于其行为而非实现,其它不同属性的实现能反映出该原始类型的行为
同样也能够避免为每个通用接口的实例重复编写文档
构造函数应当返回一个接口值而非实现的类型
例如在hash
库中,crc32.NewIEEE
和adler32.New
都返回接口类型hash.Hash32
,如果要使用Adler32算法
替换CRC-32
,只需修改构造函数调用即可, 其余代码则不受算法改变的影响
同样的方式能将crypto
包中多种联系在一起的流密码算法与块密码算法分开,crypto/cipher
包中的Block
接口指定了块密码算法的行为,它为单独的数据块提供加密,和bufio
包类型,任何实现了该接口的密码包都能被用于构造以Stream
为接口表示的流密码,而无需知道块密码的细节
1 | type Block interface { |
1 | // 计数器模式CTR流定义 |
NewCTR
的应用并不仅限于特定的加密算法和数据源,它适用于任何对Block
接口和Stream
的实现
因为它们返回接口值,所以用其它加密模式来代替CTR
只需做局部的更改
构造函数的调用过程必须被修改,但由于其周围的代码只将它看做Stream
,因此它们不会注意到其中的区别
接口和方法
因为几乎任何类型都能添加方法,所以几乎任何类型都能满足一个接口
1 | // http 包中定义了 Handler 接口 |
ResponseWriter
接口提供了对响应客户端请求的方法的访问
由于这些方法包含了标准的Write
方法,因此http.ResponseWriter
可用于任何io.Writer
适用的场景Request
结构体包含已解析的客户端请求
1 | // 假设所有的 HTTP 请求都是 GET |
1 | // 将一个服务器添加到 URL 树的一个节点上 |
Counter
不一定要是结构体,也可以是整数type Counter int
但是接收者必须是指针类型
,增量操作对于调用者才是可见的
1 | // 当页面被访问时 |
1 | // 输出调用服务器二进制程序时使用的实参 /args |
1 | // 为函数写一个方法 |
HandlerFunc
是具有ServeHTTP
方法的类型,因此该类型的值就能处理HTTP
请求
接收者是一个函数f
,而该方法调用f
接收者变成了一个信道,而方法通过该信道发送消息
1 | // 让 ArgServer 拥有合适的签名 |
1 | // ArgServer 和 HandlerFunc 拥有了相同的签名 |
当/args
页面被访问时,绑定到该页面的处理程序就有了值ArgServer
和类型HandlerFunc
HTTP
服务器会以ArgServer
为接收者,调用该类型的ServeHTTP
方法ServeHTTP
方法会反过来调用ArgServer
(通过f(c, req)
),实参就会被显示出来
接口只是方法的集合,而几乎任何类型都能定义方法。Interfaces are just sets of methods, which can be defined for (almost) any type.