golang使用高阶函数优化业务功能

业务描述

两个接口(新增Tag和更新Tag),在业务层均需要添加两个校验,校验Tag信息是否重复和Tag的数据中的编码是否重复。

基本实现

方式

对应的增加两个校验的函数/方法,在接口实现中依次调用两个函数/方法进行校验。

优缺点

实现简单;但重复代码多,后期再增加其他校验,扩展性较差。

高阶函数方式一

方式

因为业务方法参数相同,业务层新增一个方法,包含一个函数类型参数(该函数即最终业务函数,新增/更新Tag),业务层新增方法:

Go 复制代码
func (t *TagUseCase) ValidateTag(ctx context.Context, req *Tag, f func(context.Context, *Tag) error) error {
   //校验一
   exists, err := t.repo.ValidateAppIdFieldExists(ctx, req.AppId, req.Field, req.IdString)
   if err != nil {
      return err
   }
   if exists {
      return errors.New(fmt.Sprintf("已存在:%v", req.Field))
   }
   //校验二
   flag, code := t.ValidateCodeUnique(req.Children)
   if flag {
      return errors.New(fmt.Sprintf("编码重复:%v", code))
   }
   //执行业务目标函数
   return f(ctx, req)
}

服务层调用:

Go 复制代码
func (s *TagService) CreateTag(ctx context.Context, req *pb.CreateTagRequest) (*pb.OperationTagReply, error) {
    //s.tuc.CreateTag为目标函数
	err := s.tuc.ValidateTag(ctx, tag, s.tuc.CreateTag)
	if err != nil {
		return nil, errors.New(0, err.Error(), "failed!")
	}
	return &pb.OperationTagReply{Msg: "success"}, nil
}

优缺点

通过将目标函数参数化,将校验抽取到了一个方法中,后期如果增加其他校验,只需修改ValidateTag方法即可,有点类似于Java中的静态代理。重复代码很少,扩展性较好。但如果不同的业务需要的校验不完全相同,则存在问题。

高阶函数方式二

方式

通过中间件实现(借鉴Kratos框架middleware),校验函数和目标函数的入参和返回值均相同(即使不同,可通过golang的闭包将函数包装成需要的函数签名),将其都作为一次处理,将所有的处理链接起来再执行。

定义中间件Handler类型:

Go 复制代码
定义中间件
type Handler func(ctx context.Context, req interface{}) error  #定义handler类型,即定义中间件和最终执行方法/函数的的声明
type Middleware func(Handler) Handler   # 定义Middle类型,即入参和返回值为相同类型的Handler,用于后面将其链接起来
# 将各中间件链接起来,next即最终要执行的函数,通过反向遍历的方式,将中间件按照添加的顺序依次链接,最先添加的在最外层,最先执行,结构类似:func(func(func(next)))
func Chain(m ...Middleware) Middleware {
   return func(next Handler) Handler {
      for i := len(m) - 1; i >= 0; i-- {
         next = m[i](next)
      }
      return next
   }
}

在业务层创建中间件:

Go 复制代码
func (t *TagUseCase) ValidatorTagExists() tagmiddleware.Middleware {
   return func(handler tagmiddleware.Handler) tagmiddleware.Handler {
      return func(ctx context.Context, req interface{}) (err error) {
         if v, ok := req.(*Tag); ok {
            exists, err := t.repo.ValidateAppIdFieldExists(ctx, v.AppId, v.Field, v.IdString)
            if err != nil {
               return err
            }
            if exists {
               return errors.New(fmt.Sprintf("该标签field已存在:%v", v.Field))
            }
         }
         return handler(ctx, req)
      }
   }
}

调用方式一:

Go 复制代码
#添加中间件:在业务层的结构体增加一个middleware字段,创建结构体时,将需要的中间件添加到该属性中
t.middleware = []tagmiddleware.Middleware{t.ValidatorTagExists(), t.ValidatorTagCodeRepeat()}

#提供给需要中间件的方法/函数调用: Chain首先获取到执行链,最终传入h进行调用
func (t *TagUseCase) Middleware(h tagmiddleware.Handler) tagmiddleware.Handler {
   return tagmiddleware.Chain(t.middleware...)(h)
}
Go 复制代码
最终调用:首先构建中间件,UpdateTag是最终要执行的方法。middleware(ctx,tag) 执行中间件和目标方法
middleware := s.tuc.Middleware(func(ctx context.Context, disposeReq interface{}) error {
   return s.tuc.UpdateTag(ctx, disposeReq.(*biz.Tag))
})
err = middleware(ctx, tag)

调用方式二:方式一提前指定了要执行那些中间件,不够灵活

Go 复制代码
#最终调用:在调用时指定要执行那些中间件。
middlewarex := tagmiddleware.Chain([]tagmiddleware.Middleware{s.tuc.ValidatorTagExists(), s.tuc.ValidatorTagCodeRepeat()}...)(func(ctx context.Context, disposeReq interface{}) error {
			return s.tuc.UpdateTag(ctx, disposeReq.(*biz.Tag))
		})
err = middlewarex(ctx, tag)

优缺点

将校验和目标函数都作为handler处理,在调用时可以自定义设置要进行那些校验,灵活性高。但后期增加新的校验时,需要在多个调用的位置将新的校验handler添加到执行链中。

相关推荐
资深web全栈开发4 小时前
并查集(Union-Find)套路详解
leetcode·golang·并查集·unionfind
moxiaoran57536 小时前
Go语言的递归函数
开发语言·后端·golang
朝花不迟暮6 小时前
Go基础-闭包
android·开发语言·golang
西京刀客8 小时前
go语言-切片排序之sort.Slice 和 sort.SliceStable 的区别(数据库分页、内存分页场景注意点)
后端·golang·sort·数据库分页·内存分页
黄昏单车9 小时前
golang语言基础到进阶学习笔记
笔记·golang·go
moxiaoran575318 小时前
Go语言结构体
开发语言·后端·golang
Tony Bai1 天前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
小徐Chao努力1 天前
Go语言核心知识点底层原理教程【变量、类型与常量】
开发语言·后端·golang
锥锋骚年1 天前
go语言异常处理方案
开发语言·后端·golang
moxiaoran57531 天前
Go语言的map
开发语言·后端·golang