
本文介绍在 go(gin + gorm)项目中,如何用泛型友好的函数式抽象替代面向对象的“基类继承”思路,消除 json 绑定与数据库写入逻辑的重复,兼顾类型安全与可维护性。
本文介绍在 go(gin + gorm)项目中,如何用泛型友好的函数式抽象替代面向对象的“基类继承”思路,消除 json 绑定与数据库写入逻辑的重复,兼顾类型安全与可维护性。
Go 语言不支持传统面向对象中的继承机制,因此无法通过嵌入(embedding)实现类似“基类 Base 调用子类字段”的行为。你遇到的问题——Base.Add() 方法中 b.Context.BindJSON(b) 实际绑定的是 *Base 类型而非 *Shopper——正是由于方法接收者始终是嵌入字段本身(*Base),而非外围结构体(*Shopper)。Go 的嵌入仅提升方法可见性,不改变接收者类型,也不支持运行时多态。
✅ 正确做法:用函数替代方法,利用接口和泛型提升复用性与类型安全
自 Go 1.18 起,推荐使用泛型函数替代裸 interface{},既保留灵活性,又获得编译期类型检查:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
// add.go
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// Add binds JSON and persists the entity to DB — type-safe and reusable
func Add[T any](ctx *gin.Context, db *gorm.DB, entity *T) {
if err := ctx.ShouldBindJSON(entity); err != nil {
ctx.JSON(400, gin.H{"error": "invalid request body", "details": err.Error()})
return
}
if err := db.Create(entity).Error; err != nil {
ctx.JSON(500, gin.H{"error": "failed to save", "details": err.Error()})
return
}
ctx.JSON(201, gin.H{"message": "created successfully", "data": entity})
}
在 Handler 中直接调用:
// handler.go
func createShopperHandler(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var shopper Shopper // 注意:声明为变量,而非指针;Add 函数内部会取地址
Add(c, db, &shopper) // ✅ 编译期确保 shopper 实现了 JSON 可序列化结构
}
}
? 关键注意事项:
- 不要滥用嵌入模拟继承:Go 的嵌入是组合(composition),不是继承(inheritance)。强行套用 OOP 模式会导致语义错乱和调试困难。
- 优先使用 ShouldBindJSON 而非 BindJSON:前者在失败时不自动中止中间件链,便于统一错误处理。
- *显式传入 `gorm.DB`**:避免全局 DB 实例依赖,提升测试性与模块解耦。
-
泛型约束可进一步增强安全性(可选):若需限定仅支持 GORM 模型,可定义约束接口:
type Model interface { TableName() string // 或其他 GORM 相关方法 } func Add[T Model](ctx *gin.Context, db *gorm.DB, entity *T) { ... }
? 总结:Go 的抽象应基于组合、接口与泛型,而非类层级。将通用流程(解析 → 校验 → 存储 → 响应)封装为参数化函数,不仅消除了重复代码,还使逻辑更清晰、更易单元测试、更符合 Go 的简洁哲学。










