
Java 接口 | Go 接口 | |
|---|---|---|
声明方式 | implements 显式声明 | 隐式实现,不用声明 |
继承关系 | 支持多继承接口 | 支持,通过组合嵌入 |
零值 | null 引用异常 | nil 接口,可安全调用 |
设计哲学 | 类型契约,编译期强制 | 行为契约,运行期满足 |
Go 接口的核心只有一句话:"我不关心你是什么,我只关心你能做什么。"
这不是语法糖,这是架构思维。
Go 标准库里,最强大的接口往往只有一个方法:
gotype Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}io.Reader 一个方法,却能组合出 io.ReadCloser、io.ReadWriter、io.ReadWriteCloser……
设计原则:一个接口只定义一个行为。 多了就拆,这比继承灵活一百倍。
go// ❌ 反面:大而全的接口,谁都实现不了
type UserService interface {
CreateUser(ctx context.Context, u *User) error
GetUser(ctx context.Context, id int64) (*User, error)
UpdateUser(ctx context.Context, u *User) error
DeleteUser(ctx context.Context, id int64) error
SendEmail(ctx context.Context, u *User) error
// ... 还有20个方法
}
// ✅ 正面:拆成小接口,按需组合
type UserReader interface {
GetUser(ctx context.Context, id int64) (*User, error)
}
type UserWriter interface {
CreateUser(ctx context.Context, u *User) error
UpdateUser(ctx context.Context, u *User) error
DeleteUser(ctx context.Context, id int64) error
}
type Notifier interface {
SendEmail(ctx context.Context, u *User) error
}
// 需要全部能力?组合即可
type UserService interface {
UserReader
UserWriter
Notifier
}小接口 = 高组合性 = 低耦合。 这是 Go 架构灵活性的根源。
依赖倒置原则(DIP)在 Go 里的体现:
go// ❌ 硬依赖具体类型
func ProcessOrder(db *MySQL) error {
return db.Save(order)
}
// ✅ 依赖接口,调用方决定用什么
func ProcessOrder(store OrderStore) error {
return store.Save(order)
}
type OrderStore interface {
Save(o *Order) error
}但注意——不是所有东西都要抽象成接口。
go// 过度抽象:一个只有一个实现的接口,毫无意义
type StringGetter interface {
Get() string
}
type Name struct {
first string
}
func (n Name) Get() string { return n.first }
// ✅ 直接用 Name 就够了,没必要加接口判断标准:这个接口是否会有多个实现?未来会不会有? 如果答案是否定的,别加。
nil 接口的零值特性这是 Go 接口最被低估的特性:
govar r io.Reader // r == nil,但 r 不是"空"
// ✅ nil 接口可以安全传参,不会 panic
func Copy(dst io.Writer, src io.Reader) (int64, error) {
// 如果 src 是 nil,Read 返回 (0, io.EOF),逻辑自洽
return io.Copy(dst, src)
}
// 调用方可以传 nil 表示"不需要"
Copy(os.Stdout, nil) // 合法,相当于什么都不读利用这一点,可以优雅地实现可选依赖:
gotype Server struct {
Logger Logger // 可以为 nil
Metrics Metrics // 可以为 nil
Cache Cache // 可以为 nil
}
func (s *Server) Handle(req *Request) {
if s.Logger != nil {
s.Logger.Log("request received")
}
if s.Metrics != nil {
s.Metrics.Inc("requests")
}
// ...
}不需要"空对象模式",不需要
NullLogger,nil就是最好的默认值。
Go 接口支持嵌入(embedding),这是组合优于继承的语言级实现:
gotype 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 ReadWriteCloser struct {
Reader
Writer
Closer
}go// 核心接口:只定义"插件能做什么"
type Plugin interface {
Name() string
Init(ctx context.Context) error
Execute(ctx context.Context, data interface{}) error
}
// 不同插件实现同一接口
type LogPlugin struct{}
func (p *LogPlugin) Name() string { return "logger" }
func (p *LogPlugin) Init(ctx context.Context) error { return nil }
func (p *LogPlugin) Execute(ctx context.Context, data interface{}) error {
log.Printf("[%s] %v", p.Name(), data)
return nil
}
type AuthPlugin struct{}
func (p *AuthPlugin) Name() string { return "auth" }
func (p *AuthPlugin) Init(ctx context.Context) error { return nil }
func (p *AuthPlugin) Execute(ctx context.Context, data interface{}) error {
// 认证逻辑
return nil
}
// 核心引擎:完全不关心具体插件是什么
type Engine struct {
plugins []Plugin
}
func (e *Engine) Register(p Plugin) {
e.plugins = append(e.plugins, p)
}
func (e *Engine) Run(ctx context.Context, data interface{}) error {
for _, p := range e.plugins {
if err := p.Init(ctx); err != nil {
return fmt.Errorf("plugin %s init failed: %w", p.Name(), err)
}
if err := p.Execute(ctx, data); err != nil {
return fmt.Errorf("plugin %s exec failed: %w", p.Name(), err)
}
}
return nil
}引擎代码零耦合任何具体插件。新增插件?一行
Register搞定。这就是接口组合的威力。
interface{} 的正确打开方式interface{}(即 any)不是万能药,用错就是类型灾难。
go// JSON 反序列化是 interface{} 的最佳场景
func Decode(data []byte, v interface{}) error {
return json.Unmarshal(data, v) // v 必须是 *T,不是 interface{}
}
// 调用
var result map[string]interface{}
Decode(data, &result) // ✅ 传入具体类型的指针ok 形式,别 panicgo// ❌ 危险:断言失败直接 panic
func getInt(v interface{}) int {
return v.(int) // 如果 v 不是 int,程序崩
}
// ✅ 安全:先判断,再使用
func getInt(v interface{}) (int, bool) {
i, ok := v.(int)
if !ok {
return 0, false
}
return i, true
}
// ✅ 类型开关:处理多种可能
func process(v interface{}) {
switch val := v.(type) {
case string:
fmt.Println("string:", val)
case int:
fmt.Println("int:", val)
case []int:
fmt.Println("slice:", val)
default:
fmt.Println("unknown type")
}
}interface{} 逃避类型系统go// 这不是灵活,这是放弃了 Go 的类型安全
type Config struct {
Data map[string]interface{} // 什么都能塞,什么都查不出
}
// ✅ 正确做法:定义结构体,让编译器帮你检查
type Config struct {
DB DBConfig
Cache CacheConfig
Server ServerConfig
}go// ❌ 传统写法:一堆 if-else
func CalculatePrice(order *Order, userType string) float64 {
if userType == "vip" {
return order.Amount * 0.8
} else if userType == "new" {
return order.Amount * 0.9
}
return order.Amount
}
// ✅ 接口策略:开闭原则,新增策略不改原有代码
type PricingStrategy interface {
Price(amount float64) float64
}
type VIPStrategy struct{}
func (s VIPStrategy) Price(a float64) float64 { return a * 0.8 }
type NewUserStrategy struct{}
func (s NewUserStrategy) Price(a float64) float64 { return a * 0.9 }
type RegularStrategy struct{}
func (s RegularStrategy) Price(a float64) float64 { return a }
func CalculatePrice(order *Order, strategy PricingStrategy) float64 {
return strategy.Price(order.Amount)
}go// 业务层完全不知道底层用的是什么数据库
type UserRepo interface {
GetByID(ctx context.Context, id int64) (*User, error)
Create(ctx context.Context, u *User) error
}
type UserService struct {
repo UserRepo // 依赖接口,不依赖实现
}
func NewUserService(repo UserRepo) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) GetUser(ctx context.Context, id int64) (*User, error) {
return s.repo.GetByID(ctx, id)
}
// main.go 里注入具体实现
func main() {
db := postgres.NewDB() // 具体实现
repo := postgres.NewUserRepo(db) // 实现了 UserRepo
svc := NewUserService(repo) // 注入
svc.GetUser(context.Background(), 1)
}
// 切换到 Mongo?只改 main.go,业务代码一行不动
// repo := mongo.NewUserRepo(db)反模式 | 问题 | 正确做法 |
|---|---|---|
接口只有一个实现却加了接口 | 过度抽象,增加阅读成本 | 直接用具体类型 |
interface{} 满天飞 | 丢失类型安全,运行时才报错 | 定义具体结构体 |
接口方法过多(>5个) | 违反单一职责,难以实现 | 拆成多个小接口 |
循环依赖接口 | 编译不过,设计有问题 | 重新划分模块边界 |
用接口只是为了 mock 测试 | 测试驱动是好的,但别让生产代码为测试服务 | 测试用 mock,生产用真实实现 |
Go 接口不是"类型契约",是行为契约。
它的设计精髓可以浓缩为三句话:
nil 是你的朋友——善用零值接口,实现可选依赖和优雅默认。Go 没有泛型时,靠接口撑起了高扩展架构;有了泛型后,接口依然是解耦的第一选择。因为泛型解决的是类型复用,接口解决的是模块边界——这是两个维度的问题。
下次设计 Go 架构时,先画接口,再写实现。顺序反了,重构成本翻倍。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。