You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中映射DTO时减少重复代码的优化方案问询

优化Go代码重复的方案

一、统一Store接口命名与抽象通用CRUD方法

先调整specifications下的接口定义,将通用CRUD方法的名称统一,避免ListTasks、ListTags这类带实体名的命名,改为通用名称,同时抽象出基础CRUD接口,让具体实体Store继承:

// specifications/store.go
type CRUDStore[ResultDto any] interface {
    List(ctx context.Context) ([]ResultDto, error)
    Get(ctx context.Context, id int) (ResultDto, error)
    Delete(ctx context.Context, id int) error
}

type CreateStore[CreateDto any] interface {
    Create(ctx context.Context, input CreateDto) error
}

type UpdateStore[UpdateDto any] interface {
    Update(ctx context.Context, id int, input UpdateDto) error
}

// 具体实体Store继承通用接口,保留特有方法
type TaskStore interface {
    CRUDStore[dto.TaskResult]
    CreateStore[dto.TaskCreate]
    UpdateStore[dto.TaskUpdate]
}

type TagStore interface {
    CRUDStore[dto.TagResult]
    CreateStore[dto.TagCreate]
    RenameTag(ctx context.Context, id int, input dto.TagUpdate) error
}

统一命名后,通用方法的调用逻辑不再依赖实体名称,为后续复用打下基础。

二、提取通用Handler逻辑

针对List这类重复度极高的Handler方法,提取通用函数,接收必要的依赖(列表查询函数、错误提示信息),直接生成http.HandlerFunc:

// http/handlers/common.go
func GenericListHandler[T any](listFunc func(context.Context) ([]T, error), errMsg string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        data, err := listFunc(r.Context())
        if err != nil {
            log.Err(err).Msg(errMsg)
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
        render.JSON(w, r, data)
    }
}

之后在具体Handler中直接调用这个通用函数即可:

// http/handlers/task.go
func (h TaskHandler) List() http.HandlerFunc {
    return GenericListHandler(h.store.List, "failed to retrieve tasks")
}

// http/handlers/tag.go
func (h TagHandler) List() http.HandlerFunc {
    return GenericListHandler(h.store.List, "failed to retrieve tags")
}

同理,Get、Delete这类逻辑也可以用同样方式提取通用Handler,只需传入对应的store方法和错误信息。

三、DTO的设计建议

DTO无需强行统一结构(不同实体的字段差异是合理的),但要保持命名规范一致性:

  • 创建DTO统一命名为XXXCreate(如TaskCreate、TagCreate)
  • 更新DTO统一命名为XXXUpdate(如TaskUpdate、TagUpdate)
  • 返回结果DTO统一命名为XXXResult(如TaskResult、TagResult)

若部分DTO包含通用字段(如创建时间、更新时间),可定义基础DTO结构体让具体DTO嵌入:

// models/dto/base.go
type BaseResult struct {
    ID        int       `json:"id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// models/dto/task.go
type TaskResult struct {
    BaseResult
    Title       string `json:"title"`
    Description string `json:"description"`
}

// models/dto/tag.go
type TagResult struct {
    BaseResult
    Name string `json:"name"`
}

既保留实体独特性,又减少重复的通用字段定义。

四、泛型的合理使用

无需定义包含所有CRUD的泛型接口,而是针对单一通用操作编写泛型函数(如上面的GenericListHandler)。这种方式更灵活,不会因某个实体有特殊方法(比如Tag的RenameTag)而破坏泛型通用性。

若需处理更复杂的通用逻辑(如带分页的列表查询),可扩展泛型函数:

func GenericPaginatedListHandler[T any](listFunc func(context.Context, int, int) ([]T, error), errMsg string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        page, _ := strconv.Atoi(r.URL.Query().Get("page"))
        size, _ := strconv.Atoi(r.URL.Query().Get("size"))
        
        data, err := listFunc(r.Context(), page, size)
        if err != nil {
            log.Err(err).Msg(errMsg)
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
        render.JSON(w, r, data)
    }
}

内容的提问来源于stack exchange,提问作者Wez18

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 19:13:11