Go的秘密生活:接口
Source: Dev.to
隐式契约的力量
星期二早晨雾气弥漫。伊桑端着咖啡和一小盒意式脆饼走向档案室。
埃莉诺抬起头。“今天是意式的?”
“面包师说脆饼是专为咖啡杯设计的——形式服从功能。”
她笑了。“太好了。今天我们来聊接口——Go 用来定义某个东西能做什么,而不关心它是什么的方式。”
一个简单示例
package main
import "fmt"
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow!"
}
func main() {
dog := Dog{Name: "Buddy"}
cat := Cat{Name: "Whiskers"}
fmt.Println(dog.Name, "says:", dog.Speak())
fmt.Println(cat.Name, "says:", cat.Speak())
}
输出
Buddy says: Woof!
Whiskers says: Meow!
两种不同的类型——Dog 和 Cat。它们都有返回字符串的 Speak() 方法,却是互不相关的类型。
定义接口
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow!"
}
func MakeItSpeak(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
dog := Dog{Name: "Buddy"}
cat := Cat{Name: "Whiskers"}
MakeItSpeak(dog)
MakeItSpeak(cat)
}
输出
Woof!
Meow!
Speaker 定义了一个契约:任何拥有 Speak() string 方法的类型都满足该接口。函数 MakeItSpeak 接受任意 Speaker,Dog 和 Cat 都符合条件。
注意: 在 Go 中,你永远不需要显式声明一个类型实现了某个接口。如果方法匹配,实现就是隐式的。
为什么是隐式的?
- 灵活性: 你可以定义一个接口,而已有的类型已经满足它,即使这些类型在你的接口出现之前就已经写好。
- 无需前置耦合: 类型不必声明关系,代码保持松耦合。
实际案例:写入不同目标
package main
import (
"fmt"
"os"
)
type Writer interface {
Write([]byte) (int, error)
}
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(data []byte) (int, error) {
n, err := fmt.Println(string(data))
return n, err
}
func WriteMessage(w Writer, message string) {
w.Write([]byte(message))
}
func main() {
console := ConsoleWriter{}
WriteMessage(console, "Hello, Console!")
file, _ := os.Create("output.txt")
defer file.Close()
WriteMessage(file, "Hello, File!")
}
Writer 接口只有一个方法 Write([]byte) (int, error)。
ConsoleWriter实现了它。- 标准库中的
os.File也实现了它,尽管它早于我们的接口出现。
因此,WriteMessage 可以与任何满足 Writer 的类型一起工作——控制台、文件、网络连接、内存缓冲区等。
使用接口实现多态
package main
import "fmt"
type Shape interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func PrintShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 5, Height: 3}
circle := Circle{Radius: 4}
PrintShapeInfo(rect)
PrintShapeInfo(circle)
}
输出
Area: 15.00, Perimeter: 16.00
Area: 50.27, Perimeter: 25.13
Shape 要求实现 Area() 和 Perimeter()。Rectangle 与 Circle 都满足它,所以 PrintShapeInfo 能对任意 Shape 工作,而不关心具体类型。
这类似于鸭子类型,但 Go 在 编译时 检查实现,提供类型安全。
空接口(any)
package main
import "fmt"
func PrintAnything(v any) {
fmt.Println(v)
}
func main() {
PrintAnything(42)
PrintAnything("hello")
PrintAnything(true)
PrintAnything([]int{1, 2, 3})
}
输出
42
hello
true
[1 2 3]
any 是空接口 interface{} 的别名。因为每个类型至少有零个方法,所有类型都满足它。请谨慎使用,因为它会削弱编译时的类型安全。
类型断言
package main
import "fmt"
func Describe(v any) {
// 带 comma‑ok 的类型断言
if str, ok := v.(string); ok {
fmt.Printf("String: %s (length %d)\n", str, len(str))
return
}
if num, ok := v.(int); ok {
fmt.Printf("Integer: %d (doubled: %d)\n", num, num*2)
return
}
fmt.Printf("Unknown type: %T\n", v)
}
func main() {
Describe("hello")
Describe(42)
Describe(true)
}
输出
String: hello (length 5)
Integer: 42 (doubled: 84)
Unknown type: bool
如果省略 ok 检查(str := v.(string)),当断言失败时程序会 panic。
类型切换
package main
import "fmt"
func Describe(v any) {
switch val := v.(type) {
case string:
fmt.Printf("String: %s (length %d)\n", val, len(val))
case int:
fmt.Printf("Integer: %d (doubled: %d)\n", val, val*2)
case bool:
fmt.Printf("Boolean: %t\n", val)
default:
fmt.Printf("Unknown type: %T\n", val)
}
}
func main() {
Describe("hello")
Describe(42)
Describe(true)
Describe(3.14)
}
输出
String: hello (length 5)
Integer: 42 (doubled: 84)
Boolean: true
Unknown type: float64
switch val := v.(type) 结构能够简洁地处理多种可能的具体类型。
接口层次结构(从具体到通用)
-
具体接口
type Reader interface { Read([]byte) (int, error) }只有实现了
Read([]byte) (int, error)的类型才满足Reader。 -
空接口(
any)type any interface{}每种类型都满足它,是最通用的接口。只有在真的需要接受任意类型的值时才使用它。