Go 中如何处理需要方法泛型的写法?

1 天前
 cooooing

首先是 Stream Map 的例子,需要将一个类型映射为另一个类型:

不支持这种写法:

func (s Stream[T])Map[T any, R any](stream Stream[T], mapper func(T) R) Stream[R] {
	...
}

只能写成这种,链式调用啥的就断了。有点难受

func Map[T any, R any] (stream Stream[T], mapper func(T) R) Stream[R] {
	...
}

另一个例子是 orm 的,有一个管理多数据源的类,从中获取特定类型的查询(直接返回对应类型的结果):

type DataSource struct {
	DB *sql.DB
    // 是否打印日志、执行超时时间等数据源的全局设置。大概是个工厂吧
}

func(db *DataSource) GetExcutor[T any]() *Excutor[T] {
	return &Executor[T]{DB:db.DB}
}
func (e *Executor[T]) List() ([]T, error) {
	...
}

Go 不支持这个,有没有什么好的写法实现

1011 次点击
所在节点    Go 编程语言
10 条回复
devhxy
1 天前
go 的泛型本就不完善,不要用 javaer 的思考方式去写 go
morebuff
1 天前
package db

import (
"context"
"errors"

"gorm.io/gorm"
"gorm.io/gorm/clause"
)

type Dao[T any] interface {
//Save 保存
Save(ctx context.Context, model *T) error
// Create 添加
Create(ctx context.Context, model *T) error
// CreateOrUpdate 批量添加或更新
CreateOrUpdate(ctx context.Context, model *T) error
// CreateBatch 批量添加
CreateBatch(ctx context.Context, models []T) error
// CreateBatchSize 批量添加指定大小
CreateBatchSize(ctx context.Context, models []T, size int) error
// Update 更新
Update(ctx context.Context, condition, model *T) error
//UpdaterWrapper 更新
UpdaterWrapper(ctx context.Context, wrapper *Wrapper, model *T) error
// GetByID 根据 ID 获取
GetByID(ctx context.Context, ID any) (*T, error)
// GetByCondition 根据条件获取
GetByCondition(ctx context.Context, condition T) (*T, error)
//GetByWrapper 根据条件获取
GetByWrapper(ctx context.Context, wrapper *Wrapper) (*T, error)
// Page 分页查询
Page(ctx context.Context, page, size int) ([]T, int64, error)
// PageByCondition 分页查询
PageByCondition(ctx context.Context, page, size int, condition *T) ([]T, int64, error)
// PageByWrapper 分页查询
PageByWrapper(ctx context.Context, page, size int, wrapper *Wrapper) ([]T, int64, error)
//ListByCondition 根据条件获取多个
ListByCondition(ctx context.Context, condition T) ([]T, error)
// ListByWrapper 根据条件获取多个
ListByWrapper(ctx context.Context, wrapper *Wrapper) ([]T, error)
// Count 统计数量
Count(ctx context.Context) (int64, error)
// CountByWrapper 统计数量
CountByWrapper(ctx context.Context, wrapper *Wrapper) (int64, error)
// Delete 删除
Delete(ctx context.Context, value any) error
//DeleteByWrapper 删除
DeleteByWrapper(ctx context.Context, wrapper *Wrapper) error
}

type Curd[T any] struct {
model T
}

// Save 保存
func (c *Curd[T]) Save(ctx context.Context, model *T) error {
return DB().Model(&c.model).WithContext(ctx).Save(&model).Error
}

// Create 添加
func (c *Curd[T]) Create(ctx context.Context, model *T) error {
err := DB().Model(&c.model).WithContext(ctx).Create(&model).Error
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return errors.New("record already exists")
}
return err
}
return nil
}

// CreateOrUpdate 添加或更新
func (c *Curd[T]) CreateOrUpdate(ctx context.Context, model *T) error {
err := DB().Model(&c.model).WithContext(ctx).Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&model).Error
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return errors.New("record already exists")
}
return err
}
return nil
}

// CreateBatch 批量添加
func (c *Curd[T]) CreateBatch(ctx context.Context, models []T) error {
err := DB().Model(&c.model).WithContext(ctx).Create(&models).Error
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return errors.New("数据重复")
}
return err
}
return nil
}

// CreateBatchSize 批量添加指定大小
func (c *Curd[T]) CreateBatchSize(ctx context.Context, models []T, size int) error {
err := DB().Model(&c.model).WithContext(ctx).CreateInBatches(&models, size).Error
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return errors.New("数据重复")
}
return err
}
return nil
}

// Update 更新 传入的 model 必须包含主键
func (c *Curd[T]) Update(ctx context.Context, condition, model *T) error {
err := DB().Model(&model).WithContext(ctx).Updates(&model).Where(&condition).Error
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return errors.New("数据重复")
}
return err
}
return nil
}

// UpdaterWrapper 根据构造器更新
func (c *Curd[T]) UpdaterWrapper(ctx context.Context, wrapper *Wrapper, model *T) error {
err := DB().Model(&model).WithContext(ctx).Scopes(wrapper.Build()).Updates(&model).Error
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return errors.New("数据重复")
}
return err
}
return nil
}

// Delete 删除 value 可以为单个主键或者多个主键切片
func (c *Curd[T]) Delete(ctx context.Context, value any) error {
return DB().Model(&c.model).WithContext(ctx).Delete(&c.model, value).Error
}

// DeleteByWrapper 根据构造器删除
func (c *Curd[T]) DeleteByWrapper(ctx context.Context, wrapper *Wrapper) error {
return DB().Model(&c.model).WithContext(ctx).Scopes(wrapper.Build()).Delete(&c.model).Error
}

// GetByID 根据 ID 获取
func (c *Curd[T]) GetByID(ctx context.Context, ID any) (*T, error) {
var model T
err := DB().Model(&c.model).WithContext(ctx).Take(&model, ID).Error
if err != nil {
return nil, err
}
return &model, nil
}

// GetByCondition 根据条件获取
func (c *Curd[T]) GetByCondition(ctx context.Context, condition *T) (*T, error) {
var model T
err := DB().Model(&c.model).WithContext(ctx).Where(&condition).Take(&model).Error
if err != nil {
return nil, err
}
return &model, nil
}

// GetByWrapper 根据构造条件获取
func (c *Curd[T]) GetByWrapper(ctx context.Context, wrapper *Wrapper) (*T, error) {
var model T
err := DB().Model(&c.model).WithContext(ctx).Scopes(wrapper.Build()).Take(&model).Error
if err != nil {
return nil, err
}
return &model, nil
}

// Page 分页查询
func (c *Curd[T]) Page(ctx context.Context, page, size int) (models []T, total int64, err error) {
models = make([]T, 0)
if err = DB().Model(&c.model).WithContext(ctx).Count(&total).Scopes(Page(page, size)).Find(&models).Error; err != nil {
return models, 0, err
}
return models, total, nil
}

// PageByCondition 分页查询 根据条件
func (c *Curd[T]) PageByCondition(ctx context.Context, page, size int, condition *T) (models []T, total int64, err error) {
models = make([]T, 0)
if err = DB().Model(&c.model).WithContext(ctx).Where(&condition).Count(&total).Scopes(Page(page, size)).Find(&models).Error; err != nil {
return models, 0, err
}
return models, total, nil
}

// PageByWrapper 分页查询 根据构造器
func (c *Curd[T]) PageByWrapper(ctx context.Context, page, size int, wrapper *Wrapper) (models []T, total int64, err error) {
models = make([]T, 0)
err = DB().WithContext(ctx).Scopes(wrapper.Build()).Count(&total).Scopes(Page(page, size)).Find(&models).Error
if err != nil {
return models, 0, err
}
return models, total, nil
}

// ListByCondition 根据条件获取多个
func (c *Curd[T]) ListByCondition(ctx context.Context, condition *T) ([]T, error) {
models := make([]T, 0)
err := DB().Model(&c.model).WithContext(ctx).Where(&condition).Find(&models).Error
if err != nil {
return models, err
}
return models, nil
}

// ListByWrapper 根据构造器获取多个
func (c *Curd[T]) ListByWrapper(ctx context.Context, wrapper *Wrapper) (models []T, err error) {
models = make([]T, 0)
err = DB().Model(&c.model).WithContext(ctx).Scopes(wrapper.Build()).Find(&models).Error
if err != nil {
return models, err
}
return models, nil
}

// Count 统计数量
func (c *Curd[T]) Count(ctx context.Context) (total int64, err error) {
if err = DB().WithContext(ctx).Model(&c.model).Count(&total).Error; err != nil {
return 0, err
}
return total, nil
}

// CountByWrapper 统计数量 根据构造器
func (c *Curd[T]) CountByWrapper(ctx context.Context, wrapper *Wrapper) (total int64, err error) {
if err = DB().WithContext(ctx).Scopes(wrapper.Build()).Count(&total).Error; err != nil {
return 0, err
}
return total, nil
}





//用例
type memberDao struct {
db.Curd[model.Member]
}
cooooing
1 天前
@morebuff 有完整的 github 仓库地址吗,学习下
Dorathea
1 天前
没能理解你说的
比如 "链式调用", 你指的是 "class.method().method()" 么?
那应该是可以的啊 [代码]( https://go.dev/play/p/LVRcU1G3kCQ)
还有你说的

这个看起来在上面的代码里也有?
scopeccsky1111
1 天前
@cooooing #3 下面的方式是支持的啊, 只是不支持方法中 func (s *Struct) method[T any]() 这种形式的
morebuff
1 天前
@cooooing 我自己项目中用的,模仿 mybatis-plus 封装了一点,平时用着方便
package db

import (
"context"

"gorm.io/gorm"
)

type Wrapper struct {
db *gorm.DB
}

func BuildWrapper() *Wrapper {
return &Wrapper{db: DB()}
}

// DB 获取原始 DB 和错误的方法
func (w *Wrapper) DB() *gorm.DB {
return w.db
}

func (w *Wrapper) GetError() error {
return w.db.Error
}

// EQ 等于
func (w *Wrapper) EQ(column string, value any) *Wrapper {
w.db = w.db.Where(column+" = ?", value)
return w
}

// NE 不等于
func (w *Wrapper) NE(column string, value any) *Wrapper {
w.db = w.db.Where(column+" <> ?", value)
return w
}

// GT 大于
func (w *Wrapper) GT(column string, value any) *Wrapper {
w.db = w.db.Where(column+" > ?", value)
return w
}

// GE 大于等于
func (w *Wrapper) GE(column string, value any) *Wrapper {
w.db = w.db.Where(column+" >= ?", value)
return w
}

// LT 小于
func (w *Wrapper) LT(column string, value any) *Wrapper {
w.db = w.db.Where(column+" < ?", value)
return w
}

// LE 小于等于
func (w *Wrapper) LE(column string, value any) *Wrapper {
w.db = w.db.Where(column+" <= ?", value)
return w
}

// Like 模糊查询
func (w *Wrapper) Like(column string, value any) *Wrapper {
w.db = w.db.Where(column+" LIKE ?", value)
return w
}

// NotLike 模糊查询
func (w *Wrapper) NotLike(column string, value any) *Wrapper {
w.db = w.db.Where(column+" NOT LIKE ?", value)
return w
}

// LikeLeft 模糊查询
func (w *Wrapper) LikeLeft(column string, value string) *Wrapper {
w.db = w.db.Where(column+" LIKE ?", "%"+value)
return w
}

// LikeRight 模糊查询
func (w *Wrapper) LikeRight(column string, value string) *Wrapper {
w.db = w.db.Where(column+" LIKE ?", value+"%")
return w
}

// NotLikeLeft 模糊查询
func (w *Wrapper) NotLikeLeft(column string, value string) *Wrapper {
w.db = w.db.Where(column+" NOT LIKE ?", "%"+value)
return w
}

// NotLikeRight 模糊查询
func (w *Wrapper) NotLikeRight(column string, value string) *Wrapper {
w.db = w.db.Where(column+" NOT LIKE ?", value+"%")
return w
}

// IN 批量查询
func (w *Wrapper) IN(column string, values any) *Wrapper {
w.db = w.db.Where(column+" IN ?", values)
return w
}

// NotIN 批量查询
func (w *Wrapper) NotIN(column string, values any) *Wrapper {
w.db = w.db.Not(column+" IN ?", values)
return w
}

// Between 区间查询
func (w *Wrapper) Between(column string, before, after any) *Wrapper {
w.db = w.db.Where(column+" BETWEEN ? AND ?", before, after)
return w
}

// NotBetween 区间查询
func (w *Wrapper) NotBetween(column string, before, after any) *Wrapper {
w.db = w.db.Where(column+" NOT BETWEEN ? AND ?", before, after)
return w
}

// Or 或者
func (w *Wrapper) Or(condition, value any) *Wrapper {
w.db = w.db.Or(condition, value)
return w
}

// OrderBy 排序 desc 是否倒序
func (w *Wrapper) OrderBy(column string, desc bool) *Wrapper {
if desc {
w.db = w.db.Order(column + " DESC")
} else {
w.db = w.db.Order(column + " ASC")
}
return w
}

// OrderByAsc 排序
func (w *Wrapper) OrderByAsc(column string) *Wrapper {
w.db = w.db.Order(column + " ASC")
return w
}

// OrderByDesc 排序 desc 是否倒序
func (w *Wrapper) OrderByDesc(column string) *Wrapper {
w.db = w.db.Order(column + " DESC")
return w
}

// GroupBy 分组
func (w *Wrapper) GroupBy(column string) *Wrapper {
w.db = w.db.Group(column)
return w
}

// Limit 限制条数
func (w *Wrapper) Limit(limit int) *Wrapper {
w.db = w.db.Limit(limit)
return w
}

// Offset 偏移量
func (w *Wrapper) Offset(offset int) *Wrapper {
w.db = w.db.Offset(offset)
return w
}

// Columns 指定字段
func (w *Wrapper) Columns(columns ...string) *Wrapper {
w.db = w.db.Select(columns)
return w
}

// Page 分页
func (w *Wrapper) Page(page, size int) *Wrapper {
w.db = w.db.Scopes(Page(page, size))
return w
}

// Scopes 使用构造器
func (w *Wrapper) Scopes(fn func(db *gorm.DB) *gorm.DB) *Wrapper {
w.db = w.db.Scopes(fn)
return w
}

// IsNull 判断字段为 NULL
func (w *Wrapper) IsNull(column string) *Wrapper {
w.db = w.db.Where(column + " IS NULL")
return w
}

// IsNotNull 判断字段不为 NULL
func (w *Wrapper) IsNotNull(column string) *Wrapper {
w.db = w.db.Where(column + " IS NOT NULL")
return w
}

// Joins 连接查询
func (w *Wrapper) Joins(query string, args ...interface{}) *Wrapper {
w.db = w.db.Joins(query, args...)
return w
}

// Find 查询
func (w *Wrapper) Find(dest interface{}) error {
return w.db.Find(dest).Error
}

// First 查询第一条
func (w *Wrapper) First(dest interface{}) error {
return w.db.First(dest).Error
}

// Count 统计
func (w *Wrapper) Count(count *int64) error {
return w.db.Count(count).Error
}

// Build 构建查询
func (w *Wrapper) Build() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where(w.db.Statement.Clauses["WHERE"].Expression)
}
}

// Clone 克隆当前 Wrapper
func (w *Wrapper) Clone() *Wrapper {
return &Wrapper{db: w.db.Session(&gorm.Session{})}
}

// WithContext 设置上下文
func (w *Wrapper) WithContext(ctx context.Context) *Wrapper {
w.db = w.db.WithContext(ctx)
return w
}

// Transaction 添加事务
func (w *Wrapper) Transaction(fn func(tx *Wrapper) error) error {
return w.db.Transaction(func(tx *gorm.DB) error {
txWrapper := &Wrapper{db: tx}
return fn(txWrapper)
})
}

// Scan 扫描结果到目标
func (w *Wrapper) Scan(dest any) error {
return w.db.Scan(dest).Error
}

// Prepare 预编译 SQL 语句
func (w *Wrapper) Prepare() *Wrapper {
w.db = w.db.Session(&gorm.Session{PrepareStmt: true})
return w
}

// Select 仅选择需要的字段
func (w *Wrapper) Select(fields ...string) *Wrapper {
w.db = w.db.Select(fields)
return w
}
cooooing
1 天前
@Dorathea
链式调用是指在 map 的时候改变了 stream 的泛型具体类型,比如 int->string

Stream[int].
Filter(...). // 正常链式调用
Map(func(i int) string { return fmt.Sprintf("item %d", i) }). // 这里 map 返回的是 Stream[string]
ToArray()

只能写成
Map(Stream[int].Filter(...)).ToArray()

第二个那个是从一个没有泛型的类,创建返回一个有泛型的其他类,就需要第一个类的方法上使用泛型传入类型信息

类似的还有这种写法:
type Processor interface {
Process[T any](data T)
}
sakeven
1 天前
试了一下,只能定义成这样:

```go

package main

import (
"fmt"
)

type Stream[T any, R any] struct {
data []T
}

func (s Stream[T,R])Map(stream Stream[T,R], mapper func(T) R) Stream[R,T] {
new := Stream[R,T]{data: make([]R, len(s.data))}

for i := range s.data {
new.data[i] = mapper(s.data[i])
}
return new
}

func main() {
intStream := Stream[int, string]{data: []int{1, 2, 3, 4, 5}}
squaredStream := intStream.Map(intStream, func(x int) string { return fmt.Sprintf("a %d", x) })
fmt.Println(squaredStream.data)
}

```


R 的类型需要在创建 Stream 实例的时候,声明好具体类型。
Gilfoyle26
1 天前
感觉 go 已经很克制了,像隔壁的 swift 整出 200 多个关键字出来
strobber16
1 天前

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1155517

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX