接口满足规则
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 43 课,共 107 课。
虽然 Go 使用鸭子类型来实现隐式接口满足,但仍有一些具体规则决定某个类型是否真正满足某个接口。了解这些规则可以帮助你避免不易察觉的 bug。
最重要的规则与指针接收者有关。如果方法使用指针接收者定义,那么只有指向该类型的指针才满足接口要求,值本身不满足:
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // 指针接收者
return "Saved: " + d.Name
}
func Process(s Saver) {
fmt.Println(s.Save())
}
由于 Save() 使用指针接收者,只有 *Document 才满足 Saver:
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // 可以运行:指针满足接口要求
// Process(doc) // 编译错误:值不满足接口要求
}
不过,反过来会更灵活。如果方法使用值接收者,值类型和指针类型都满足该接口。调用值接收者方法时,Go 会自动解引用指针:
func (d Document) Info() string { // 值接收者
return d.Name
}
// Document 和 *Document 都满足要求实现 Info() 的接口
这种不对称性存在的原因是,Go 总能从指针获取值(通过解引用),但不一定总能从值获取指针(该值可能无法寻址)。记住这条规则,可以避免在使用接口时遇到令人困惑的编译器错误。
挑战
简单我们来构建一个配置系统,演示指针接收者和值接收者如何影响接口实现。你将创建一些类型,接收者的选择会决定值、指针或两者是否都能用于某个接口。
你将把代码分成三个文件:
config.go:定义一个Configurable接口,要求实现两个方法:GetValue() string和SetValue(string)。然后创建两种配置类型:ReadOnlyConfig,包含一个Value字段:为GetValue()使用值接收者(返回 Value),为SetValue()使用指针接收者(更新 Value)Setting,包含一个Data字段:两个方法都使用值接收者(GetValue 返回 Data,SetValue 只打印 "Cannot modify",不做任何更改)
processor.go:创建一个名为ProcessConfig的函数,它接收一个Configurable和一个新的字符串值。函数应使用GetValue()打印当前值,使用新值调用SetValue(),然后再次打印该值,以显示发生的任何变化。main.go:从输入中读取配置信息,并演示接口实现规则:- 创建一个
ReadOnlyConfig,并将一个指针传给ProcessConfig(这是必需的,因为SetValue使用指针接收者) - 创建一个
Setting,并将值本身直接传给ProcessConfig(这是可行的,因为两个方法都使用值接收者)
- 创建一个
将提供以下输入:
- 第 1 行:ReadOnlyConfig 的初始值
- 第 2 行:要为 ReadOnlyConfig 设置的新值
- 第 3 行:Setting 的初始值
- 第 4 行:尝试为 Setting 设置的新值
你的 ProcessConfig 函数应按以下格式打印:
Current: [value]
Current: [value after SetValue]例如,输入 debug、production、localhost 和 remote 后,输出应为:
Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost注意,ReadOnlyConfig 的值确实发生了变化(因为我们传入了一个指针),而 Setting 保持不变(它的 SetValue 使用值接收者,无法修改原始值)。关键在于,单独的 ReadOnlyConfig 值无法实现 Configurable 接口,只有指针可以;而 Setting 值可以直接使用,因为它的所有方法都使用值接收者。
亲手试试
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 读取输入
scanner.Scan()
rocInitial := scanner.Text()
scanner.Scan()
rocNew := scanner.Text()
scanner.Scan()
settingInitial := scanner.Text()
scanner.Scan()
settingNew := scanner.Text()
// TODO: 用 rocInitial 的值创建一个 ReadOnlyConfig
// TODO: 向 ProcessConfig 传入指针(因为 SetValue 的接收者是指针)
// TODO: 用 settingInitial 的值创建一个 Setting
// TODO: 直接向 ProcessConfig 传入值(因为两个方法的接收者都是值类型)
// 使用这些变量,避免出现变量未使用的错误
_ = rocInitial
_ = rocNew
_ = settingInitial
_ = settingNew
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器