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添加到执行链中。

相关推荐
q567315231 小时前
使用CPR库编写的爬虫程序
开发语言·爬虫·golang·音视频
Pyroyster2 小时前
【Go语言圣经1.1】
开发语言·后端·golang
YGGP3 小时前
【每日八股】Golang篇(三):关键字(下)
开发语言·后端·golang
熬了夜的程序员3 小时前
Go 语言封装 HTTP 请求的 Curl 工具包
后端·程序人生·http·golang
Benaso17 小时前
Java,Golang,Rust 泛型的大体对比小记
java·golang·rust
怪力乌龟1 天前
go语言设计模式-适配器模式
设计模式·golang·适配器模式
螺旋式上升abc1 天前
GO语言学习笔记
笔记·学习·golang
至暗时刻darkest1 天前
go mod文件 项目版本管理
开发语言·后端·golang
银河小铁骑plus1 天前
Go学习笔记:基础语法6
笔记·学习·golang
cheniie1 天前
Go加spy++隐藏窗口
golang·隐藏窗口