Skip to main content

接口与组合

Go 用隐式接口与组合表达多态,而不是类继承树。

接口与隐式实现

接口只声明方法集;类型只要实现了全部方法,就自动满足该接口,无需 implements

type Speaker interface {
Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string {
return d.Name + ": woof"
}

func say(s Speaker) {
fmt.Println(s.Speak())
}

say(Dog{Name: "Buddy"})

接口值内部是「动态类型 + 动态值」。两边都为 nil 时接口才是 nil——常见坑:

var d *Dog = nil
var s Speaker = d
fmt.Println(s == nil) // false:接口非 nil,动态类型是 *Dog

any / interface

空接口可承接任意值(Go 1.18+ 别名 any):

var v any = 42
v = "hi"

switch x := v.(type) {
case int:
fmt.Println("int", x)
case string:
fmt.Println("string", x)
default:
fmt.Println("other")
}

类型断言:x, ok := v.(string)。能用具体类型或小接口就不要滥用 any

组合(embedding)优于继承

结构体可嵌入另一类型,提升其字段/方法:

type Engine struct{}

func (Engine) Start() { fmt.Println("start") }

type Car struct {
Engine
Brand string
}

c := Car{Brand: "X"}
c.Start() // 提升自 Engine

这是组合Car 有一个 Engine,不是「Car is-a Engine」的继承树。也可嵌入接口。

小接口习惯

标准库偏爱小接口,例如:

// 简化示意
type Reader interface {
Read(p []byte) (n int, err error)
}

实现一个方法就能接入大量 I/O 工具。设计自己的接口时:

  1. 站在调用方需要什么行为来定义
  2. 接口尽量小;大接口难实现、难 mock
  3. 接受接口、返回具体类型(常见经验法则)

方法集提醒:指针接收者实现的方法,只有 *T 满足接口;值接收者则 T*T 都可能满足。

小结

  1. 实现是隐式的,看方法集是否匹配
  2. any 要配合类型断言 / 类型 switch
  3. 用 embedding 做组合,不建深继承
  4. 优先小接口,向标准库对齐

上一篇:包与模块初识 · 下一篇:并发