Go中接口实现依赖倒置的核心是调用方只依赖接口而非具体类型,解决测试难绕过真实I/O和更换依赖时需大量修改的问题。

Go 里用接口实现依赖倒置,不是为了套设计原则,而是为了解决两个实际问题:测试时没法绕过真实 I/O,以及换数据库或第三方服务时要改一堆结构体字段。核心就一条——让调用方只依赖接口,不认具体类型。
为什么直接 new *sql.DB 或 *http.Client 就埋了雷
常见错误是把具体客户端当字段塞进结构体:
type UserService struct {
db *sql.DB
client *http.Client
}
这会导致:
- 单元测试必须连真实 DB 或启 mock server,跑一次测试慢、不稳定、难复现失败
- 想把 MySQL 换成 PostgreSQL?得改
UserService字段类型、构造函数、所有用到db的地方 -
*http.Client带着默认超时和 Transport,测试中无法控制请求延迟或模拟网络错误
定义接口时只留被调用的方法,别贪多
接口不是越全越好,而是越窄越稳。看谁在调用、调什么,就抽象什么:
- 如果业务层只查用户,就定义
UserReader,只含FindByID(ctx context.Context, id int64) (*User, error) - 别加
Update()或Delete()——除非当前逻辑真用到了 - 方法参数必须带
context.Context,否则测试时无法注入取消信号或超时控制 - 接口名体现角色,比如
CacheStore比RedisClient更准确,它不暴露底层是 Redis 还是 memory
mock 实现别硬编码逻辑,用字段控制返回
写 mock 结构体时,避免在方法里写 if-else 分支判断 ID 返回不同值:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
type MockUserRepo struct {
FindByIDReturn *User
FindByIDError error
}
func (m *MockUserRepo) FindByID(ctx context.Context, id int64) (*User, error) {
return m.FindByIDReturn, m.FindByIDError
}
这样在测试 setup 阶段就能干净地控制行为:
mock := &MockUserRepo{
FindByIDReturn: &User{Name: "alice"},
FindByIDError: nil,
}
svc := NewUserService(mock)
容易踩的坑:
- 接口方法接收者是指针(
func (u *User) Save()),那 mock 类型也得是指针*MockUser,否则运行时报missing method Save - 别用
interface{}当接口传——它没有方法约束,编译不报错,一运行就 panic - mock 实例别跨测试复用,每个测试 new 一个,避免状态污染
依赖注入时传 interface,不是 struct 指针
构造函数签名决定你能不能替换成 mock:
- ❌ 错误:
func NewUserService(db *sql.DB) *UserService—— 调用方被绑死在*sql.DB - ✅ 正确:
func NewUserService(repo UserReader) *UserService—— 测试传&MockUserRepo{},生产传&SQLUserRepo{db: realDB} - 结构体字段也存 interface:
type UserService struct { repo UserReader },不是db *sql.DB
最常被忽略的一点:接口定义的位置。别把它扔进 mock 包里,而应放在被测业务包内(或 shared 接口包),否则就成了“为 mock 而抽象”,脱离了真实协作契约。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










