隐式实现
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 26 课,共 107 课。
在 Java 或 C# 等许多面向对象语言中,你必须使用 implements 这样的关键字,显式声明某个类型实现了一个接口。Go 采用了不同的方式:接口实现是隐式的。
只要实现接口的所有方法,类型就满足该接口。无需在任何地方声明这种关系。编译器会自动判断。
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter 隐式实现了 Writer 接口
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
注意,FileWriter 上没有 implements Writer 声明。由于 FileWriter 有一个与接口签名匹配的 Write(data string) int 方法,它会自动满足 Writer 接口。
这种设计带来了一个强大的优势:你可以在类型已经存在后再定义接口。如果第三方库中的某个类型包含你需要的方法,你可以创建一个它已经满足的接口,而无需修改原始代码。
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // 能正常运行,因为 FileWriter 实现了 Writer 接口
}
隐式实现让 Go 代码保持解耦和灵活。类型无需提前了解接口,这样更容易用独立的部分组合系统。
挑战
简单让我们构建一个消息发送系统,演示 Go 中的类型如何隐式满足接口。你将创建不同的消息发送器类型,它们都满足同一个接口,无需显式声明这种关系。
你要将代码分到两个文件中:
messengers.go:定义一个Messenger接口,其中只有一个方法Send(message string) string。然后创建两个会隐式满足此接口的结构体:EmailMessenger,包含一个Address字段(字符串)SMSMessenger,包含一个PhoneNumber字段(字符串)
Send方法,返回描述发送内容的字符串。两个结构体都不应显式声明自己实现了Messenger:Go 会根据方法签名自动判断。main.go:创建一个名为DeliverMessage的函数,该函数接收任意Messenger和一个消息字符串,然后返回调用Send的结果。从输入中读取联系信息,创建这两种消息发送器,并使用你的函数通过它们分别发送消息。
输入如下:
- 第 1 行:电子邮件地址
- 第 2 行:电话号码
- 第 3 行:要发送的消息
你的 Send 方法应返回以下格式的字符串:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
例如,输入 alice@example.com、555-1234 和 Hello! 时,输出应为:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!这里的关键在于,你的 DeliverMessage 函数可以接收任意 Messenger,而 EmailMessenger 和 SMSMessenger 只需拥有匹配的 Send 方法,就能满足该接口。代码中完全不需要 implements 关键字:Go 的隐式实现机制会处理这一切。
亲手试试
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: 创建 DeliverMessage 函数,接收任意 Messenger
// 和一个消息字符串,然后返回调用 Send 的结果
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取邮箱地址
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// 读取电话号码
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// 读取消息
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: 用邮箱地址创建一个 EmailMessenger
// TODO: 用电话号码创建一个 SMSMessenger
// TODO: 使用 DeliverMessage,通过每个 Messenger 发送消息
// 并打印结果
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器