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
相关产品推荐
相关产品推荐

