返回 Notes
未分类NOTE

泛形

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
}

常规写法

  1. 只有 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 = 123
    b = [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],方法内才能使用类型 T
func (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
}

泛型

  1. 先说一下泛型的语法

    struct or func在名后增加一个 [T any]都是在此类型/方法的作用域中声明了一个名为T的泛型对象.

    下游调用这个上游struct的时候, 必须使用 [] 明确声明上游T是什么, 例如代码中的[model.User]

    因为下游已经给上游声明了T是什么, 下游调用上游的func时候就能使用对应的对象

  2. 解释一下这段代码的作用

    上游定义了一个 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 内嵌了该实例,自动获得了上述强类型方法