Day 5|interface 设计原则与工程实践(Go 的灵魂) [Go 语言 30 天系统学习计划]

学习目标
从工程角度理解 **interface 在 Go 中的真实地位**
掌握 Go 接口的核心设计原则(尤其是“小接口”)
正确使用接口进行解耦,而不是制造复杂度
学会在真实项目中“该不该定义接口”的判断方法

一、为什么 interface 是 Go 的灵魂

解耦、扩展、测试、架构边界的核心工具

没有 interface 的 Go 项目:
– 依赖实现
– 难测试
– 难扩展
– 难演进

二、interface 的本质

1. interface 是“行为集合”

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

说明:

  • interface 只描述“能做什么”
  • 不关心“怎么做”

2. 隐式实现(极其重要)

type FileReader struct{}

func (f *FileReader) Read(p []byte) (int, error) {
    return 0, nil
}
var r Reader = &FileReader{}

结论:

实现者不需要知道接口的存在


三、小接口原则(Go 接口设计的核心)

1. 好接口的特征

  • 方法少(1~3 个最理想)
  • 语义清晰
  • 名字来自“使用者视角”

2. 标准库示例

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

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

它们组合成:

type ReadWriter interface {
    Reader
    Writer
}

结论:

小接口 + 组合 = 强表达力


四、不要“提前设计接口”

错误示例(新手常犯)

type UserService interface {
    CreateUser()
    UpdateUser()
    DeleteUser()
    GetUser()
}

问题:

  • 接口太大
  • 不一定真的需要
  • 强迫实现者实现无关方法

正确思路

接口应该由“调用方”定义,而不是实现方


五、接口是为“使用者”设计的

示例:错误的接口位置

// 在 service 包中定义
type UserRepo interface {
    Save()
}

正确做法:

// 在使用 repo 的地方定义
type UserSaver interface {
    Save()
}

结论:

谁依赖谁,接口就应该放在哪里


六、interface 与 struct 的关系

1. struct 是实现,interface 是能力

type EmailSender struct{}

func (e *EmailSender) Send(msg string) error {
    return nil
}
type Sender interface {
    Send(msg string) error
}
func Notify(s Sender) {
    s.Send("hello")
}

2. 面向接口编程

func NewService(sender Sender) *Service {
    return &Service{sender: sender}
}

结论:

不依赖具体类型,只依赖能力


七、interface 与指针的高频陷阱

1. 指针接收者必须用指针断言

type Foo struct{}

func (f *Foo) Do() {}

var i interface{} = &Foo{}
_, ok1 := i.(Foo)   // false
_, ok2 := i.(*Foo)  // true

2. nil interface 陷阱(极其重要)

var f *Foo = nil
var i interface{} = f

fmt.Println(i == nil) // false

原因:

  • interface = 类型 + 值
  • 类型存在,接口就非 nil

八、interface 在测试中的巨大价值

1. 使用 mock 实现

type Sender interface {
    Send(msg string) error
}

type MockSender struct{}

func (m *MockSender) Send(msg string) error {
    fmt.Println("mock send:", msg)
    return nil
}
func TestNotify() {
    Notify(&MockSender{})
}

九、什么时候应该用 interface?

推荐使用 interface 的场景

  • 多种实现
  • 需要 mock
  • 边界层(repo / adapter / client)
  • 框架、插件系统

不推荐使用 interface 的场景

  • 只有一个实现
  • 非边界内部逻辑
  • 数据结构(DTO / Model)

十、自检问题(进入 Day 6 前必须能回答)

  1. 为什么 Go 强调小接口?
  2. 接口应该由谁来定义?
  3. interface 和 struct 的边界在哪里?
  4. nil interface 为什么容易出 bug?
  5. 什么情况下“不要用 interface”?

预告:Day 6

Day 6|类型断言与 type switch(interface 的“解包”)

你将深入理解:

  • interface 的运行期结构
  • 类型断言为什么会 panic
  • type switch 的工程价值
  • 如何写“安全、不脆弱”的断言代码

发表评论