Repository navigation
06 interface and abstraction
omeyang edited this page Sep 16, 2026
·
1 revision
本文档定义 Go 项目中接口设计、抽象时机、泛型使用和抽象层次的规范。
- 面向接口编程 - 依赖抽象而非具体实现
- 小接口原则 - 接口应该小而专注(ISP)
- 合理抽象 - 必须有 2 种及以上实现时才抽象
- 善用泛型 - Go 1.18+ 泛型应该简化通用代码
- 清晰的抽象层次 - 禁止抽象泄漏
客户端禁止依赖它不需要的方法。
错误:大接口
type Repository interface {
FindByID(ctx context.Context, id string) (*Order, error)
Create(ctx context.Context, order *Order) error
Update(ctx context.Context, order *Order) error
Delete(ctx context.Context, id string) error
Count(ctx context.Context) (int64, error)
}正确:拆分为小接口
type Finder interface {
FindByID(ctx context.Context, id string) (*Order, error)
}
type Creator interface {
Create(ctx context.Context, order *Order) error
}
type Updater interface {
Update(ctx context.Context, order *Order) error
}
// 组合接口(按需组合)
type ReadRepository interface {
Finder
}
type WriteRepository interface {
Creator
Updater
}
type Repository interface {
ReadRepository
WriteRepository
}Go 惯例:应该优先使用单方法接口。
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
}接口名禁止包含包名:
// 错误
package order
type OrderRepository interface { ... } // order.OrderRepository
// 正确
package order
type Repository interface { ... } // order.Repository必须依赖接口而非具体实现(DIP):
// 错误:依赖具体实现
type OrderProcessor struct {
mongoRepo *MongoRepository // 依赖具体实现
}
// 正确:依赖接口
type OrderProcessor struct {
repo Repository // 依赖抽象
}
func NewOrderProcessor(repo Repository) *OrderProcessor {
return &OrderProcessor{repo: repo}
}应该使用构造函数注入:
type Matcher struct {
repo Repository
cache Cache
logger Logger
}
func NewMatcher(repo Repository, cache Cache, logger Logger) *Matcher {
return &Matcher{
repo: repo,
cache: cache,
logger: logger,
}
}核心规则:必须有 2 种及以上实现时才抽象。
应该抽象的场景:
- 已有 2 种及以上实现
- 明确需要支持多种实现
- 外部依赖(便于测试)
禁止抽象的场景:
- 只有 1 种实现,且未来不会变
- 简单的数据结构
// 错误:不必要的抽象
type IDGenerator interface {
Generate() string
}
// 正确:直接使用函数
func GenerateID() string {
return uuid.New().String()
}禁止实现还不需要的功能。
// 错误:过度抽象
type AbstractFactory interface {
CreateItem() ItemInterface
}
// 正确:简单直接
type Item struct {
ID string
Name string
}
func NewItem(id, name string) *Item {
return &Item{ID: id, Name: name}
}- 应该用于通用容器、算法、数据结构
- 禁止用于业务逻辑
- 类型参数应该不超过 2 个
通用算法:
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, predicate func(T) bool) []T {
result := make([]T, 0)
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}通用数据结构:
type Cache[K comparable, V any] struct {
data sync.Map
}
func (c *Cache[K, V]) Set(key K, value V) {
c.data.Store(key, value)
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
val, ok := c.data.Load(key)
if !ok {
var zero V
return zero, false
}
return val.(V), true
}// 错误:用泛型写业务逻辑
func ProcessItem[T ItemInterface](item T) error {
// 业务逻辑必须具体,禁止泛化
}
// 正确:业务逻辑必须具体
func ProcessItem(item *Item) error {
// 具体的业务处理
}同一个函数/模块必须保持相同的抽象层次。
// 错误:抽象层次混乱
func ProcessOrder(order *Order) error {
if err := ValidateOrder(order); err != nil {
return err
}
// 低层实现细节混在一起
db := mongo.Connect("mongodb://...")
collection := db.Database("orders").Collection("orders")
_, err := collection.InsertOne(context.Background(), order)
return err
}
// 正确:抽象层次一致
func ProcessOrder(order *Order) error {
if err := ValidateOrder(order); err != nil {
return err
}
if err := SaveOrder(order); err != nil {
return err
}
return NotifyOrderCreated(order)
}接口禁止暴露实现细节:
// 错误:暴露了 MongoDB 的 bson.M
type Repository interface {
FindByFilter(ctx context.Context, filter bson.M) (*Order, error)
}
// 正确:使用通用查询条件
type Repository interface {
FindByID(ctx context.Context, id string) (*Order, error)
}新增功能应该不修改现有代码:
// 使用策略模式替代 if-else
type Matcher interface {
Match(ctx context.Context, criteria *Criteria) (*Result, error)
}
type MatcherFactory struct {
matchers map[string]Matcher
}
func (f *MatcherFactory) Register(typeName string, matcher Matcher) {
f.matchers[typeName] = matcher
}
// 新增类型只需注册,不修改现有代码
factory.Register("new-type", &NewTypeMatcher{})| 耦合类型 | 说明 | 推荐度 |
|---|---|---|
| 内容耦合 | 直接访问另一个模块的内部数据 | 禁止 |
| 公共耦合 | 通过全局变量通信 | 禁止 |
| 控制耦合 | 传递控制标志 | 不应该 |
| 数据耦合 | 通过参数传递数据 | 应该 |
| 消息耦合 | 通过消息/事件通信 | 应该 |
- 接口小而专注(单一职责)
- 应该优先使用单方法接口
- 接口名禁止包含包名
- 接口应该按职责拆分
- 接口应该可组合
- 必须有 2 种及以上实现时才抽象
- 禁止为了抽象而抽象
- 必须遵循 YAGNI 原则
- 应该用泛型实现通用容器/算法
- 禁止用泛型写业务逻辑
- 类型参数应该不超过 2 个
- 同一函数抽象层次必须一致
- 禁止抽象泄漏
- 接口禁止暴露实现细节
- SOLID 原则:https://en.wikipedia.org/wiki/SOLID
- YAGNI 原则:https://martinfowler.com/bliki/Yagni.html
- Go 泛型:https://go.dev/doc/tutorial/generics
- Uber Go Style Guide:https://github.com/uber-go/guide
本页由 Maat 仓库的 scripts/sync-wiki.sh 自动生成,请勿直接编辑。