目录

Go 语言接口(Interface)

接口是 Go 语言中最核心的抽象机制之一。与其他语言不同,Go 的接口是隐式实现的——类型不需要显式声明"我实现了某个接口",只要它实现了接口要求的全部方法,就自动满足该接口。这种设计让 Go 的代码更加灵活、松耦合。本篇将全面介绍接口的定义、使用、类型断言以及最佳实践。

接口的定义

使用 type ... interface 语法定义接口,接口是一组方法签名的集合:

package main

import "fmt"

// 定义一个 Shape 接口
type Shape interface {
    Area() float64
    Perimeter() float64
}

func main() {
    // 接口是抽象的,不能直接实例化
    var s Shape
    fmt.Println(s) // <nil>
}

上面的 Shape 接口要求实现 Area()Perimeter() 两个方法。任何类型只要同时实现了这两个方法,就自动成为 Shape 接口的实现。

隐式实现(Duck Typing)

Go 不需要 implements 关键字。只要类型实现了接口中定义的所有方法,就自动满足接口:

package main

import (
    "fmt"
    "math"
)

type Shape interface {
    Area() float64
}

type Circle struct {
    Radius float64
}

// Circle 实现了 Area 方法,自动满足 Shape 接口
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

type Rect struct {
    Width, Height float64
}

// Rect 也实现了 Area 方法,同样满足 Shape 接口
func (r Rect) Area() float64 {
    return r.Width * r.Height
}

// 接收接口类型的函数
func PrintArea(s Shape) {
    fmt.Printf("面积: %.2f\n", s.Area())
}

func main() {
    c := Circle{Radius: 5}
    r := Rect{Width: 4, Height: 6}

    PrintArea(c) // 面积: 78.54
    PrintArea(r) // 面积: 24.00
}

Duck Typing:如果一个东西走起来像鸭子、叫起来像鸭子,那它就是鸭子。Go 接口同理——不关心你是什么类型,只关心你能做什么。

https://img.zhaojq.top/20260729161720394.png
接口实现关系

接口是一组方法签名

接口只声明方法的签名(参数和返回值),不包含实现。不同的类型可以用各自的方式实现同一组方法:

package main

import "fmt"

// Speaker 接口只有一个方法
type Speaker interface {
    Speak() string
}

type Dog struct{}
type Cat struct{}

func (d Dog) Speak() string   { return "汪汪!" }
func (c Cat) Speak() string   { return "喵~" }

func main() {
    animals := []Speaker{Dog{}, Cat{}}
    for _, a := range animals {
        fmt.Println(a.Speak())
    }
    // 输出:
    // 汪汪!
    // 喵~
}

空接口 interface

空接口不包含任何方法,因此所有类型都实现了空接口。在 Go 1.18+ 中,可以使用 any 作为 interface{} 的别名:

package main

import "fmt"

func main() {
    // interface{} 可以存储任意类型的值
    var anything interface{}

    anything = 42
    fmt.Println(anything) // 42

    anything = "hello"
    fmt.Println(anything) // hello

    anything = []int{1, 2, 3}
    fmt.Println(anything) // [1 2 3]
}

空接口常用于编写接受任意类型参数的通用函数(在泛型出现之前)。例如 fmt.Println 的参数类型就是 interface{}

类型断言

接口变量存储的是 (value, type) 对。类型断言用于从接口值中提取具体的底层类型:

package main

import "fmt"

func main() {
    var i interface{} = "hello"

    // 单返回值:类型不匹配时会 panic
    s := i.(string)
    fmt.Println(s) // hello

    // 安全的类型断言:value, ok 模式
    s, ok := i.(string)
    if ok {
        fmt.Println("是字符串:", s)
    }

    // 类型不匹配时,ok 为 false,不会 panic
    n, ok := i.(int)
    fmt.Println(n, ok) // 0 false
}

注意:单返回值的类型断言如果类型不匹配会引发 panic,生产代码中务必使用 value, ok 模式。

type switch

当需要对接口值进行多种类型的判断时,type switch 比连续的类型断言更简洁:

package main

import "fmt"

func describe(i interface{}) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf("整数: %d", v)
    case string:
        return fmt.Sprintf("字符串: %q", v)
    case bool:
        return fmt.Sprintf("布尔值: %t", v)
    case float64:
        return fmt.Sprintf("浮点数: %.2f", v)
    default:
        return fmt.Sprintf("未知类型: %T", v)
    }
}

func main() {
    fmt.Println(describe(42))       // 整数: 42
    fmt.Println(describe("hello"))  // 字符串: "hello"
    fmt.Println(describe(true))     // 布尔值: true
    fmt.Println(describe(3.14))     // 浮点数: 3.14
    fmt.Println(describe([]int{}))  // 未知类型: []int
}

接口组合

Go 支持通过嵌入其他接口来组合新接口:

package main

import "fmt"

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter 组合了 Reader 和 Writer
type ReadWriter interface {
    Reader
    Writer
}

// 只要同时实现了 Read 和 Write,就自动满足 ReadWriter
type Buffer struct {
    data []byte
}

func (b *Buffer) Read(p []byte) (n int, err error)  { return 0, nil }
func (b *Buffer) Write(p []byte) (n int, err error)  { return len(p), nil }

func main() {
    var rw ReadWriter = &Buffer{}
    fmt.Printf("%T 实现了 ReadWriter\n", rw)
}

常用标准库接口

Go 标准库中定义了大量接口,以下是几个最常用的:

error 接口

// error 是 Go 中最常用的接口
type error interface {
    Error() string
}

fmt.Stringer 接口

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// 实现 fmt.Stringer 接口
func (u User) String() string {
    return fmt.Sprintf("%s(%d岁)", u.Name, u.Age)
}

func main() {
    u := User{Name: "张三", Age: 25}
    fmt.Println(u) // 张三(25岁) — fmt.Println 会自动调用 String()
}

io.Reader 和 io.Writer

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    // strings.NewReader 返回的类型实现了 io.Reader
    r := strings.NewReader("Hello, Go!")

    buf := make([]byte, 5)
    n, err := r.Read(buf)
    if err != nil && err != io.EOF {
        panic(err)
    }
    fmt.Printf("读取 %d 字节: %s\n", n, buf[:n]) // 读取 5 字节: Hello

    // 继续读取
    n, err = r.Read(buf)
    fmt.Printf("读取 %d 字节: %s\n", n, buf[:n]) // 读取 5 字节: , Go!
}

接口最佳实践

小接口原则

Go 推崇小接口——接口只包含 1-2 个方法,职责单一。标准库中的 io.Readerio.Writerfmt.Stringer 都是单方法接口。

// 好的设计:小而专注的接口
type Writer interface {
    Write(p []byte) (n int, err error)
}

// 不好的设计:接口过于庞大
type DoEverything interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
    Close() error
    Seek(offset int64, whence int) (int64, error)
    // ... 更多方法
}

接受接口,返回结构体

函数参数尽量接收接口(提高灵活性),返回值尽量返回具体类型(让调用者知道确切类型):

package main

import "fmt"

type Greeter interface {
    Greet() string
}

type EnglishGreeter struct{}

func (g EnglishGreeter) Greet() string { return "Hello!" }

// 参数接收接口,提高灵活性
func PrintGreeting(g Greeter) {
    fmt.Println(g.Greet())
}

func main() {
    PrintGreeting(EnglishGreeter{})
}

在需要时定义接口,而非提前

不要一开始就定义大量接口。当有多个类型需要以相同方式处理时,再提取接口:

package main

import "fmt"

// 先有具体类型
type Circle struct{ Radius float64 }
type Square struct{ Side float64 }

// 当发现多处需要 Area() 时,再提取接口
type Shaper interface {
    Area() float64
}

func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius }
func (s Square) Area() float64 { return s.Side * s.Side }

func TotalArea(shapes []Shaper) float64 {
    total := 0.0
    for _, s := range shapes {
        total += s.Area()
    }
    return total
}

func main() {
    shapes := []Shaper{
        Circle{Radius: 5},
        Square{Side: 4},
    }
    fmt.Printf("总面积: %.2f\n", TotalArea(shapes))
}

总结

本篇介绍了 Go 接口的核心机制:接口通过方法签名定义行为规范,类型通过隐式实现满足接口,无需显式声明;空接口可以存储任意类型;类型断言和 type switch 用于从接口值中提取具体类型;接口组合让我们可以灵活构建更大的抽象。遵循小接口原则,在需要时定义接口,是写出优雅 Go 代码的关键。