
Go 接口不是类型约束,是行为契约
Go 的 interface{} 不是 Java/C# 那种“必须显式实现”的接口。它不关心你是什么类型,只看你有没有提供对应方法签名。只要结构体、指针、甚至函数类型实现了所有接口方法,就自动满足该接口——没声明、没继承、没 implements 关键字。
常见错误是写完一个结构体后,还去加一句 var _ MyInterface = (*MyStruct)(nil) 来“强制检查”,这其实只是编译期断言,不是必需步骤。真正该做的是:先定义接口(比如 Reader),再让具体类型自然满足它。
- 接口越小越好,
io.Reader就只有Read([]byte) (int, error)一个方法 - 避免提前抽象:别为了“看起来解耦”而先写一堆空接口,等真有 2 个以上实现再提炼
- 值接收者 vs 指针接收者影响接口满足性:如果接口方法用指针接收者,那只有
*T能满足,T值类型不行
用空接口 interface{} 时,别忘了类型断言和反射开销
interface{} 是万能容器,但不是万能解药。它把类型信息擦除了,想拿回来就得靠类型断言(v, ok := x.(string))或 reflect 包。一旦频繁做这个动作,性能会掉,代码也容易崩。
典型场景是写通用日志函数、配置解析器或 JSON 反序列化后的字段访问。这时候要注意:
立即学习“go语言免费学习笔记(深入)”;
- 类型断言失败返回零值 +
false,不加ok判断直接用会 panic,错误信息是panic: interface conversion: interface {} is int, not string - 嵌套 map[string]interface{} 解析 JSON 后,每层取值都要断言,建议尽早转成结构体(
json.Unmarshal直接到 struct) -
fmt.Printf("%v", x)内部用了反射,对大对象或深度嵌套结构体较慢,生产环境慎用于高频日志
接口嵌套不是继承,是组合行为
Go 接口支持嵌套,比如 type ReadWriter interface { Reader; Writer },但这不是面向对象里的“子类继承父类”,只是语法糖,等价于把 Reader 和 Writer 的所有方法平铺进来。
实际用的时候容易踩坑:
- 嵌套接口不会带来任何运行时开销,但会让接口膨胀。别为了“复用”把无关方法塞进一个大接口里
- 两个接口嵌套了同名方法(如都含
Close() error),没问题;但如果签名不同(比如参数个数或类型不一致),编译直接报错:duplicate method Close - 嵌套不传递实现:即使
A实现了Reader,B嵌套了Reader,A也不会自动满足B—— 满足关系只看方法集,不看嵌套路径
测试 mock 接口时,别手动写大段 fake 实现
写单元测试要 mock 依赖(比如数据库、HTTP 客户端),最直接方式是让 fake 类型实现接口。但手写 func (f *FakeDB) Query(...) { ... } 很累,尤其接口方法多时。
更轻量的做法:
- 用匿名结构体快速实现单测所需最小行为:
db := &FakeDB{QueryFunc: func(...) {...}},然后在方法里调用f.QueryFunc - 第三方工具如
gomock或counterfeiter自动生成 mock,但注意:生成代码会变多,且接口一改就要重跑 - 优先考虑“真依赖轻量化”:比如用
sqlmock拦截*sql.DB调用,而不是 mock 整个database/sql接口
最难的其实是判断什么时候该抽接口:不是所有依赖都需要抽象,比如标准库的 time.Now,用函数变量替代比包级别接口更干净。










