使用链表进行批量执行操作

最近在项目中学到一种挺有意思的编程技巧。这里记录一下

场景

在创建一个抽奖的时候,前端会携带许许多多的数据过来,这些数据有抽奖本身的、抽奖配置的、奖项信息的、参与方式的、助力方式的、创建人信息的、创建人联系方式的等等。

这里面每一种都需要校验,有些条件还是互斥的,有些条件的判定依赖与其他条件的结果,所以无法只靠结构体 tag 去使用 gValid 库校验,并且在验证的时候还需要考虑验证的顺序。

解决方案

通过阅读项目的源码以后,发现是实现了一个链表,这个链表有 3 个方法:

  • AddToChainHead( Handler ): 添加结点到链表的表头
  • AddToChainTail( Handler ): 添加结点到链表的表尾
  • Iterator( Param Structure ): 遍历链表

结点的数据域是一个 Handler,这个结构体必须实现 Handler 接口,在遍历链表时需要调用其校验方法。

Handler 接口中只有一个 Handle( Param Structure ) error 校验方法。

在创建抽奖的时候,初始化这个链表,将校验器逐个加入链表,最后遍历链表,在链表中调用结点的中的校验器的 Handle 校验方法执行。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package chain

// Handler 接口,参数放在 req 这个结构体中
type Handler interface {
    Handle(*Req) error
}

type node struct {
    Data Handler  // 数据域
    Next *Node    // 指针域
}

type chain struct {
    HeadNode *Node // 头结点
    TailNode *Node // 尾结点
}

// 创建链表
type NewChain() *chain {
    return &chain{}
}

// 添加结点到链表头部
func (c *chain) AddToHead(h Handler) {
    node := &node{
        Data: h,
        Next: nil,
    }
    if c.HeadNode == nil {
        c.HeadNode = node
        c.TailNode = node
        return
    }
    node.Next = c.HeadNode
    c.HeadNode = node
}

// 添加链表到尾部
func (c *chain) AddToTail(h Handler) {
    node := &node{
        Data: h,
        Next: nil,
    }
    if c.HeadNode == nil || c.TailNode == nil {
        c.HeadNode = node
        c.TailNode = node
        return
    }
    c.TailNode.Next = node
}

func (c *chain) Iterator(req *Req) error {
    curr := c.HeadNode
    for curr != nil {   // 遍历链表
        err := curr.Data.Handle(req); // 执行 Handle 方法
        if err != nil {
            return err
        }
        curr = curr.Next
    }
    return nil
}

这样链表的定义好了。使用者在使用的时候,需要实现 Handle 方法、添加到链表、调用 Iterator 即可。

下面创建一个结构体去实现 Handle 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package create

import (
    "errors"
    "time"
    "verify"
)

// 实例化一个验证器
var InfoVerifier = &infoVerifier{}

type infoVerfier struct {}

// 实现 Handle 方法
func (l *infoVerifier) Handle(req *Req) error {
    if req.Info.CreateTime < time.Now().Unix() {
        return errors.New("创建时间错误")
    }
    if req.Info.Uid < 1 {
        return errors.New("创建人id错误")
    }
    if req.Info.ManagerId < 1 {
        return errors.New("管理员id错误")
    }
    if !verify.IsBadMan(req.Info.Uid) {
        return errors.New("请稍后再试")
    }
    return nil
}

在创建抽奖的时候,初始化链表、添加到链表、调用 Iterator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package create

func CreateLottery(req *Req) {
    c := chain.NewChain()       // 初始化链表
    c.AddToHead(InfoVerifier)   // 添加验证器到链表
    err := c.Iterator(req)      // 遍历所有验证器
    if err != nil {
        response.JsonExitError(400, error)
    }
    ...
}

总结

这样的方法实现了非常好的扩展性。编写链表结构的人不需要考虑后面使用者的参数多么复杂,反正都统一放在 Param Structure 中,只要求使用者一定要实现 Handle 方法。然后在遍历方法 Iterator 中遍历链表并调用 Handle 就行。

使用者只需要创建一个结构体,实现 Handle 方法使结构体成为一个校验器,然后使用 AddToXXX 加入链表,调用 Iterator 方法即可。