Go中newXXX函数返回接口而非结构体的原因及合理性分析
为什么返回接口而非结构体,这种方式是否是良好实践?
先看你给出的代码:
type _ABitOfEverythingServer struct { v map[string]*examples.ABitOfEverything m sync.Mutex } type ABitOfEverythingServer interface { examples.ABitOfEverythingServiceServer // 接口 examples.StreamServiceServer // 接口 } func newABitOfEverythingServer() ABitOfEverythingServer { //<-为何不返回_ABitOfEverythingServer,这种方式是否可取? return &_ABitOfEverythingServer{ v: make(map[string]*examples.ABitOfEverything), // 省略部分代码 } }
为什么不直接返回_ABitOfEverythingServer结构体?
核心原因是隐藏内部实现细节:
- 注意到结构体名是
_ABitOfEverythingServer,以下划线开头意味着它是未导出的类型,外部包根本无法直接引用它。就算你想返回这个结构体,外部代码也没法声明对应的变量来接收返回值。 - 退一步说,就算结构体是导出的,返回接口也能把内部的字段(比如
v、m)完全隐藏起来,外部调用者只能通过接口定义的方法和服务器交互,不能直接操作内部状态,避免了意外的副作用。
这种返回接口的方式是良好的编程实践吗?
绝对是,这是Go语言中非常推荐的设计思路,尤其是在编写对外提供的API或库时,好处非常多:
- 遵循依赖倒置原则:调用者依赖的是抽象的接口(
ABitOfEverythingServer),而不是具体的结构体实现。后续如果需要替换服务器的实现(比如改成基于数据库存储而不是内存map),只要新的结构体实现了这个接口,调用代码完全不需要修改。 - 保持API稳定性:只要接口的方法签名不变,内部结构体的字段、实现逻辑怎么改都不会影响外部调用者。比如你后续给结构体加了新字段,或者修改了锁的逻辑,外部代码完全感知不到。
- 接口组合的灵活性:这里
ABitOfEverythingServer组合了两个接口,返回这个接口后,调用者可以直接使用这两个接口的所有方法,不用关心具体结构体是如何整合这两组方法的。 - 测试友好:在单元测试中,你可以很容易地编写一个
ABitOfEverythingServer接口的mock实现,替换掉真实的服务器,方便测试依赖这个服务器的代码。
总结一下,这种设计既保证了内部实现的封装性,又给代码的扩展和维护带来了极大的灵活性,是Go中非常经典的良好实践。
内容的提问来源于stack exchange,提问作者williambest
相关产品推荐
相关产品推荐

