泛形
golang 泛型h2
type UserRepo struct { DB *gorm.DB}
func (r *UserRepo) Create(ctx context.Context, user *model.User) error { return r.DB.WithContext(ctx).Create(user).Error}
func (r *UserRepo) FindByID(ctx context.Context, id uint) (*model.User, error) { var user model.User if err := r.DB.WithContext(ctx).First(&user, id).Error; err != nil { return nil, err } return &user, nil}
type ProductRepo struct { DB *gorm.DB}
func (r *ProductRepo) Create(ctx context.Context, p *model.Product) error { return r.DB.WithContext(ctx).Create(p).Error // 逻辑一模一样!}
func (r *ProductRepo) FindByID(ctx context.Context, id uint) (*model.Product, error) { var p model.Product if err := r.DB.WithContext(ctx).First(&p, id).Error; err != nil { return nil, err } return &p, nil}常规写法
-
只有
DB *gorm.DB的struct被重复声明,Create等func 被重复实现, 其实只有一个入参user *model.User不同: 为什么要这么写, 为什么这么死板, 因为代码逻辑中需要用到一个 类型:
*model.User,*model.User是一个struct, golang是一个强静态类型语言, 不像python是一个天生泛型能力的, 如果是Python, 只需要这么做就行# 伪代码def Create(db, data):db.create(data)Python 是不需要明确声明一行代码中的一个对象/变量是什么类型的, 只有真正运行到时候才需要判断, 例如
a = 123b = [123123]if a > b:pass
// BaseRepo 定义通用基础仓储type BaseRepo[T any] struct { DB *gorm.DB}
// NewBaseRepo 构造函数,返回指定 T 类型的 BaseRepo 指针func NewBaseRepo[T any](db *gorm.DB) *BaseRepo[T] { return &BaseRepo[T]{ DB: db, }}
// 接收者必须写 *BaseRepo[T],方法内才能使用类型 Tfunc (r *BaseRepo[T]) Create(ctx context.Context, model *T) error { return r.DB.WithContext(ctx).Create(model).Error}
func (r *BaseRepo[T]) FindByID(ctx context.Context, id uint) (*T, error) { var dest T // GORM 会自动填充到 dest 中 if err := r.DB.WithContext(ctx).First(&dest, id).Error; err != nil { return nil, err } return &dest, nil}// UserRepo 内嵌具体的 BaseRepo[model.User]type UserRepo struct { *BaseRepo[model.User]}
// 这里 UserRepo 已经是明确的实体,不再需要 [T any]func NewUserRepo(db *gorm.DB) *UserRepo { return &UserRepo{ // 通过 [model.User] 实例化泛型构造函数 BaseRepo: NewBaseRepo[model.User](db), }}
// 如果 UserRepo 有特定业务方法,可以直接拓展func (r *UserRepo) FindByEmail(ctx context.Context, email string) (*model.User, error) { var user model.User if err := r.DB.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil { return nil, err } return &user, nil}泛型
-
先说一下泛型的语法
struct or func在名后增加一个[T any]都是在此类型/方法的作用域中声明了一个名为T的泛型对象.下游调用这个上游
struct的时候, 必须使用[]明确声明上游T是什么, 例如代码中的[model.User]因为下游已经给上游声明了T是什么, 下游调用上游的func时候就能使用对应的对象
-
解释一下这段代码的作用
上游定义了一个
BaseRepo, 这个BaseRepo同时具备 一个DB *gorm.DB, 这是一个需要初始化的对象, 同时实现了一些func操作Create等下游
UserRepo想复用上游的DB *gorm.DB和Create等操作, 就是用struct的组合能力, 把BaseRepo添加到自己身上, 因为BaseRepo提供了[T], 所有把model.User传递过去, 上游的Create等操作中的代码逻辑, 就能直接作用于model.User表上了
补充说明h2
-
[T any]是声明,不是类型本身:- 定义
type BaseRepo[T any] struct时,BaseRepo严格来说不是一个完整的类型,而是一个“类型模具(Generic Type)”。 - 只有填入了实参后的
BaseRepo[model.User],在 Go 的类型系统里才算一个真正的、具体的“类型(Instantiated Type)”。
- 定义
-
内嵌字段的名字:
-
在
UserRepo结构体中:type UserRepo struct {*BaseRepo[model.User] // 匿名组合/内嵌} -
匿名字段的默认属性名就是类型名
BaseRepo,所以你在初始化时写BaseRepo: NewBaseRepo[model.User](db)是完全成立的。
-
下游定义: UserRepo 决定将 T 定为 model.User │ ▼实例化: NewBaseRepo[model.User](db) │ ▼编译期展开: BaseRepo 里的所有 T 瞬间被敲定: - Create(ctx, model *model.User) error - FindByID(ctx, id uint) (*model.User, error) │ ▼方法提升: UserRepo 内嵌了该实例,自动获得了上述强类型方法