Go的秘密生活:接口

发布: (2025年12月2日 GMT+8 13:46)
6 min read
原文: Dev.to

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!

两种不同的类型——DogCat。它们都有返回字符串的 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 接受任意 SpeakerDogCat 都符合条件。

注意: 在 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()RectangleCircle 都满足它,所以 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{}

    每种类型都满足它,是最通用的接口。只有在真的需要接受任意类型的值时才使用它。

Back to Blog

相关文章

阅读更多 »