首页 / Go 语言入门教程 / 接口定义与隐式实现

Go 语言入门教程

接口定义与隐式实现

本教程共 80 篇 · 第 41 篇 · 更新于 2026-07-27 · 约 7 分钟阅读

GoGo 入门教程接口Interface隐式实现鸭子类型多态

41. 接口定义与隐式实现

本节目标:搞懂 Go 接口是什么,学会定义接口,理解隐式实现和鸭子类型的设计理念。

接口是什么

接口(Interface)是一组方法签名的集合。它定义了「能做什么」,但不关心「怎么做」。

打个比方:你有一个 USB 接口,只要设备能插进去就能用,不管是鼠标、键盘还是 U 盘。接口只规定形状(方法签名),不规定内部实现。

type Speaker interface {
    Speak() string
}

这个接口说:「只要你有 Speak() 方法并且返回 string,你就是 Speaker」。

隐式实现

Go 的接口实现是隐式的。你不需要写 implements 关键字,只要一个类型实现了接口要求的所有方法,它就自动满足这个接口。

package main

import "fmt"

// 定义接口
type Speaker interface {
    Speak() string
}

// 定义类型
type Dog struct {
    Name string
}

// Dog 实现了 Speak 方法
func (d Dog) Speak() string {
    return d.Name + ": 汪汪汪"
}

type Cat struct {
    Name string
}

// Cat 也实现了 Speak 方法
func (c Cat) Speak() string {
    return c.Name + ": 喵喵喵"
}

func main() {
    animals := []Speaker{
        Dog{Name: "Rex"},
        Cat{Name: "Whiskers"},
    }

    for _, a := range animals {
        fmt.Println(a.Speak())
    }
    // Rex: 汪汪汪
    // Whiskers: 喵喵喵
}

DogCat 都没有声明自己实现了 Speaker,但它们都有 Speak() string 方法,所以都能放进 []Speaker 里。

Note

这叫「鸭子类型」:如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。Go 在编译时检查类型是否实现了接口的所有方法,比运行时鸭子类型更安全。

接口的好处:松耦合

接口最大的价值是解耦。调用方只依赖接口,不依赖具体类型,换实现时不影响调用方:

package main

import "fmt"

// 接口定义行为
type Notifier interface {
    Notify(message string) error
}

// 邮件通知
type EmailNotifier struct{}

func (e EmailNotifier) Notify(message string) error {
    fmt.Println("发送邮件:", message)
    return nil
}

// 短信通知
type SMSNotifier struct{}

func (s SMSNotifier) Notify(message string) error {
    fmt.Println("发送短信:", message)
    return nil
}

// 调用方只依赖接口
func sendMessage(n Notifier, msg string) {
    n.Notify(msg)
}

func main() {
    sendMessage(EmailNotifier{}, "你好") // 发送邮件: 你好
    sendMessage(SMSNotifier{}, "你好")   // 发送短信: 你好
}

sendMessage 函数不知道也不关心传进来的是邮件还是短信,它只要求参数实现了 Notifier 接口。以后要加微信通知,写个新类型就行,sendMessage 一行都不用改。

多方法接口

接口可以有多个方法:

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

一个类型必须实现所有方法才算满足接口。少一个都不行。

Tip

Go 社区有个建议:接口越小越好。只定义你需要的方法,不要塞一堆方法进去。小接口更容易被满足,组合性更强。标准库的 io.Reader 只有一个 Read 方法,但几乎所有涉及读取的类型都实现了它。

任何类型都能实现接口

不只是结构体,任何自定义类型都能实现接口:

type Score int

func (s Score) String() string {
    return fmt.Sprintf("%d分", int(s))
}

// Score 实现了 fmt.Stringer 接口
func main() {
    s := Score(95)
    fmt.Println(s) // 95分  Println 会自动调用 String()
}

甚至函数类型也行:

type Handler func()

func (h Handler) Run() {
    h()
}

type Runner interface {
    Run()
}

func main() {
    var r Runner = Handler(func() {
        fmt.Println("running")
    })
    r.Run() // running
}

接口组合

接口可以组合其他接口,形成更大的接口:

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
}

满足 ReadWriter 的类型必须同时满足 ReaderWriter。这个后面接口组合章节会详细讲。

小结

接口是 Go 类型系统的灵魂:

  • 接口定义方法签名集合,不提供实现
  • 实现是隐式的,不用 implements 关键字
  • 只要类型实现了接口的所有方法,就自动满足接口
  • 接口实现松耦合,调用方只依赖行为不依赖具体类型
  • 接口越小越好,用组合构建大接口

下一章讲接口值的内部结构和类型断言。