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

相关推荐
材料苦逼不会梦到计算机白富美29 分钟前
golang HTTP基础
http·golang·iphone
友大冰3 小时前
Go 语言已立足主流,编程语言排行榜24 年 11 月
开发语言·后端·golang
hummhumm4 小时前
第 10 章 - Go语言字符串操作
java·后端·python·sql·算法·golang·database
__AtYou__9 小时前
Golang | Leetcode Golang题解之第563题二叉树的坡度
leetcode·golang·题解
凡人的AI工具箱16 小时前
15分钟学 Go 第 49 天 :复杂项目开发
开发语言·人工智能·后端·算法·golang
杜杜的man18 小时前
【go从零单排】Random Numbers、Number Parsing
开发语言·python·golang
aiee19 小时前
Golang时间函数
开发语言·后端·golang
还是转转20 小时前
Go开发指南- Goroutine
开发语言·golang
蜗牛沐雨20 小时前
Go语言中的`io.Pipe`:实现进程间通信的利器
开发语言·后端·golang·进程通信·pipe
杜杜的man20 小时前
【go从零单排】泛型(Generics)、链表
开发语言·链表·golang