手撸golang 行为型设计模式 解释器模式

手撸golang 行为型设计模式 解释器模式

缘起

最近复习设计模式

拜读谭勇德的<>

本系列笔记拟采用golang练习之

解释器模式

解释器模式(Interpreter Pattern)指给定一门语言,
定义它的文法的一种表示,
并定义一个解释器
,该解释器使用该表示来解释语言中的句子。
解释器模式是一种按照规定的文法(语法)进行解析的模式,
属于行为型设计模式。

(摘自 谭勇德 <>)

场景

  • 某业务系统, 随数据量增加, 数据库访问压力日渐增大
  • 业务team希望平台team提供透明的缓存解决方案以缓解数据库压力
  • 平台team经反复讨论研究, 决定采用 解释器模式 , 直接拦截/解析/执行SQL(子集)语句, 提供透明化缓存服务

SQL(子集)文法

SQL:
  select + FIELD_LIST + from TABLE_NAME + (where BOOL_EXPRESSION)?
  
FIELD_LIST:
    *
  | COLUMN_LIST
  
COLUMN_LIST:
    COLUMN_NAME + (,COLUMN_NAME)*
  
COLUMN_NAME:
    IDENTIFIER
  
IDENTIFIER:
    [_a-zA-Z] + [_a-zA-Z0-9]*

TABLE_NAME: 
  IDENTIFIER
  
BOOL_EXPRESSION:
    STRING_FIELD = STRING_LITERAL
  | STRING_FIELD  STRING_LITERAL
  | STRING_FIELD like STRING_LITERAL
  | STRING_FIELD not like STRING_LITERAL
  | INT_FIELD = INT_LITERAL
  | INT_FIELD  INT_LITERAL
  | INT_FIELD > INT_LITERAL
  | INT_FIELD >= INT_LITERAL
  | INT_FIELD < INT_LITERAL
  | INT_FIELD <= INT_LITERAL
  | ( + BOOL_EXPRESSION + )
  | BOOL_EXPRESSION and BOOL_EXPRESSION
  | BOOL_EXPRESSION or BOOL_EXPRESSION
  
STRING_FIELD:
    IDENTIFIER
  
INT_FIELD:
    IDENTIFIER
  
STRING_LITERAL:
    \' + [^"]* + \'
  
INT_LIETRAL:
    [1-9] + [0-9]*

设计

  • IDatabase: 数据库接口
  • IDataTable: 数据表接口
  • IDataRow: 数据行接口
  • IDataField: 数据字段接口
  • IRowFilter: 数据行过滤器接口
  • tEmptyRowFilter: 静态为true/false的行过滤器
  • tExpressionRowFilter: 基于布尔表达式的行过滤器
  • Tokens: SQL(子集)记号枚举
  • Nodes: SQL(子集)语法树节点枚举
  • Chars: 词法分析辅助类
  • ISQLParser: SQL(子集)词法分析器接口
  • Lexer: 词法分析实现
  • Parser: 语法分析实现, SQL(子集)解释器的核心
  • tTokenQueue: 词法节点队列
  • tArrayStack: 基于数组实现的LIFO堆栈
  • IBoolExpression: 布尔表达式接口
  • tFieldExpression: 基于字段计算的布尔表达式
  • tLogicExpression: 基于关系(AND/OR)计算的布尔表达式
  • SaleOrder: 销售订单实体类
  • ISaleOrderService: 销售订单服务接口, 继承自IDataTable接口
  • tMockDatabase: 虚拟的数据库服务, 实现IDatabase接口
  • tMockSaleOrderService: 虚拟的销售订单服务, 实现ISaleOrderService接口

单元测试

interpreter_pattern_test.go, 模拟销售订单的保存与SQL查询

package behavioral_patterns

import (
    "learning/gooop/behavioral_patterns/interpreter"
    "testing"
)

func Test_Interpreter(t *testing.T) {
    service := interpreter.MockSaleOrderService
    service.Save(interpreter.NewSaleOrder(1, "张三", "广州", "电视", 10))
    service.Save(interpreter.NewSaleOrder(11, "张三三", "广州", "电视", 11))
    service.Save(interpreter.NewSaleOrder(2, "李四", "深圳", "冰箱", 20))
    service.Save(interpreter.NewSaleOrder(22, "李四四", "深圳", "冰箱", 21))
    service.Save(interpreter.NewSaleOrder(3, "王五", "东莞", "空调", 30))
    service.Save(interpreter.NewSaleOrder(33, "王五五", "东莞", "空调", 31))


    db := interpreter.MockDatabase
    e,rows := db.Query("select * from sale_order where (city='广州' or city='深圳') and quantity>10")
    if e != nil {
        t.Error(e)
    } else {
        for _,it := range rows {
            t.Logf("%s", it)
        }
    }
}

测试输出

$ go test -v interpreter_pattern_test.go 
=== RUN   Test_Interpreter
    interpreter_pattern_test.go:24: id=2, customer=李四, city=深圳, product=冰箱, quantity=20
    interpreter_pattern_test.go:24: id=22, customer=李四四, city=深圳, product=冰箱, quantity=21
    interpreter_pattern_test.go:24: id=11, customer=张三三, city=广州, product=电视, quantity=11
--- PASS: Test_Interpreter (0.00s)
PASS
ok      command-line-arguments  0.002s

IDatabase.go

数据库接口

package interpreter

type IDatabase interface {
    Register(table IDataTable)
    Query(sql string) (error, []IDataRow)
}

IDataTable.go

数据表接口

package interpreter

type IDataTable interface {
    Name() string
    Filter(filter IRowFilter) []IDataRow
}

IDataRow.go

数据行接口

package interpreter

type IDataRow interface {
    GetField(field string) (error,IDataField)
}

IDataField.go

数据字段接口

package interpreter

import (
    "errors"
)

type IDataField interface {
    Name() string
    DataType() DataTypes
    GetString() (error,string)
    GetInt() (error, int)
}


type DataTypes int
const StringDataType DataTypes = 1
const IntDataType DataTypes = 2


type tIntField struct {
    name string
    value int
}

func newIntField(name string, value int) *tIntField {
    return &tIntField{
        name,value,
    }
}

func (me *tIntField) Name() string {
    return me.name
}

func (me *tIntField) DataType() DataTypes {
    return IntDataType
}

func (me *tIntField) GetString() (error,string) {
    return errors.New("not implemented"), ""
}

func (me *tIntField) GetInt() (error,int) {
    return nil, me.value
}


type tStringField struct {
    name string
    value string
}

func newStringField(name string, value string) *tStringField {
    return &tStringField{
        name,value,
    }
}

func (me *tStringField) Name() string {
    return me.name
}

func (me *tStringField) DataType() DataTypes {
    return StringDataType
}

func (me *tStringField) GetString() (error,string) {
    return nil, me.value
}

func (me *tStringField) GetInt() (error,int) {
    return errors.New("not implemented"), 0
}

IRowFilter.go

数据行过滤器接口

package interpreter

type IRowFilter interface {
    Filter(row IDataRow) bool
}

tEmptyRowFilter.go

静态为true/false的行过滤器

package interpreter

type tEmptyRowFilter struct {
    accept bool
}

func newEmptyRowFilter(accept bool) IRowFilter {
    return &tEmptyRowFilter{
        accept: accept,
    }
}

func (me *tEmptyRowFilter) Filter(row IDataRow) bool {
    return me.accept
}

tExpressionRowFilter.go

基于布尔表达式的行过滤器

package interpreter


type tExpressionRowFilter struct {
    expression IBoolExpression
}

func newExpressionRowFilter(e IBoolExpression) IRowFilter {
    return &tExpressionRowFilter{
        expression: e,
    }
}

func (me *tExpressionRowFilter) Filter(row IDataRow) bool {
    return me.expression.Eval(row)
}

Tokens.go

SQL记号枚举

package tokens

type Tokens string

const Select Tokens = "select"
const Star Tokens = "*"
const Comma Tokens = ","
const From Tokens = "from"
const Where Tokens = "where"
const Identifier Tokens = "identifier"
const LB Tokens = "("
const RB Tokens = ")"
const And Tokens = "and"
const OR Tokens = "or"
const Equal Tokens = "="
const NotEqual Tokens = ""
const Greater Tokens = ">"
const GreaterEqual Tokens = ">="
const Less Tokens = "<"
const LessEqual Tokens = "<="
const Like Tokens = "like"
const NotLike Tokens = "not like"
const StringLiteral Tokens = "string_literal"
const IntLiteral Tokens = "int_literal"

Nodes.go

SQL语法树节点枚举

package nodes

type Nodes int
const TokenNode Nodes = 1
const ExpressionNode Nodes = 2

Chars.go

词法分析辅助类

package interpreter


type tChars struct {

}


func newCharsLib() *tChars {
    return &tChars{}
}

func (me *tChars) IsSpace(it rune) bool {
    switch it {
    case ' ':
        return true
    case '\t':
        return true
    case '\r':
        return true
    case '\n':
        return true
    }

    return false
}


func (me *tChars) Is09(it rune) bool {
    return it >= '0' && it = '1' && it = 'a' && it = 'A' && it <= 'Z')
}

func (me *tChars) IsUnderscore(it rune) bool {
    return it == '_'
}

func (me *tChars) IsLB(it rune) bool {
    return it == '('
}

func (me *tChars) IsRB(it rune) bool {
    return it == ')'
}

func (me *tChars) IsChar(it rune, args... rune) bool {
    for _,v := range args {
        if v == it {
            return true
        }
    }
    return false
}

var chars = newCharsLib()

ISQLParser.go

SQL词法分析器接口

package interpreter


type ISQLParser interface {
    Parse(sql string) (error,*ParseResult)
}


type ParseResult struct {
    Fields []string
    Table string
    RowFilter IRowFilter
}

func newParseResult() *ParseResult {
    return &ParseResult{
        make([]string, 0),
        "",
        nil,
    }
}

Lexer.go

SQL(子集)词法分析实现

package interpreter

import (
    "errors"
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
)


type tLexer struct {
    chars []rune
    count int
    pos int
    tokens []*tTokenNode
}

func newLexer(sql string) *tLexer {
    chars := []rune(sql)
    return &tLexer{
        chars: chars,
        count: len(chars),
        pos: 0,
        tokens: make([]*tTokenNode, 0),
    }
}


func (me *tLexer) push(it *tTokenNode) {
    me.tokens = append(me.tokens, it)
}

func (me *tLexer) Parse() (error, []*tTokenNode) {
    fnMatchingWord := func(t tokens.Tokens) bool {
        pattern := string(t)
        from := me.pos
        if me.MatchingConst(pattern) && me.MatchingSpace() {
            me.push(newTokenNode(t, string(t), from, me.pos - 1))
            return true
        }

        return false
    }

    fnMatchingOP := func(t tokens.Tokens) bool {
        pattern := string(t)
        from := me.pos
        if me.MatchingConst(pattern) {
            me.push(newTokenNode(t, string(t), from, me.pos - 1))
            return true
        }

        return false
    }

    for {
        // eof
        if me.IsEof() {
            return nil, me.tokens
        }

        me.SkipSpace()
        from := me.pos

        // select, from, where
        if fnMatchingWord(tokens.Select) || fnMatchingWord(tokens.From) || fnMatchingWord(tokens.Where) {
            continue
        }

        // start,comma
        if fnMatchingWord(tokens.Star) || fnMatchingWord(tokens.Comma) {
            continue
        }

        // and, or
        if fnMatchingWord(tokens.And) {
            continue
        } else if fnMatchingWord(tokens.OR) {
            continue
        }

        // like, not like
        if fnMatchingWord(tokens.Like) {
            continue
        } else if fnMatchingWord(tokens.NotLike) {
            continue
        }

        // (, )
        if fnMatchingOP(tokens.LB) {
            continue
        } else if fnMatchingOP(tokens.RB) {
            continue
        }

        // =,,>,>=,<,= me.count
}

func (me *tLexer) SkipSpace() {
    for {
        if me.pos >= me.count {
            break
        }

        if chars.IsSpace(me.Char()) {
            me.pos++
        } else {
            break
        }
    }
}

func (me *tLexer) MatchingConst(s string) bool {
    pattern := []rune(s)
    for i,it := range pattern {
        n := me.pos + i
        if n >= me.count {
            return false
        }
        if me.chars[n] != it {
            return false
        }
    }

    me.pos += len(pattern)
    return true
}

func (me *tLexer) Char() rune {
    if me.pos >= me.count {
        return 0
    }

    return me.chars[me.pos]
}

func (me *tLexer) MatchingSpace() bool {
    if chars.IsSpace(me.Char()) {
        me.pos++
        return true
    }
    return false
}

func (me *tLexer) MatchingString() (bool,string) {
    mark := me.pos

    if me.Char() != '\'' {
        return false, ""
    }

    i := mark
    for {
        i++

        switch me.chars[i] {
        case '\'':
            me.pos = i + 1
            return true, string(me.chars[mark:me.pos])

        default:
            if i >= me.count {
                return false, ""
            }
        }
    }
}


func (me *tLexer) MatchingIdentifier() (bool, string) {
    mark := me.pos

    c := me.Char()
    if !(chars.IsUnderscore(c) || chars.IsLetter(c)) {
        return false, ""
    }

    i := mark
    for {
        i++
        if i > mark + 30 {
            return false,""
        }

        it := me.chars[i]
        if chars.IsLetter(it) || chars.Is09(it) || chars.IsUnderscore(it) {
            continue

        } else {
            me.pos = i
            return true, string(me.chars[mark:i])
        }
    }
}


func (me *tLexer) MatchingInt() (bool, string) {
    if me.Char() == '0' {
        me.pos++
        return true, "0"
    }

    mark := me.pos

    if !chars.Is19(me.Char()) {
        return false, ""
    }

    i := mark
    for {
        i++
        if i >= me.count {
            me.pos = me.count
            return true, string(me.chars[mark:])
        }

        if i > mark + 10 {
            return false, ""
        }

        it := me.chars[i]
        if chars.Is09(it) {
            continue
        }

        if chars.IsSpace(it) {
            me.pos = i - 1
            return true, string(me.chars[mark:i])

        } else {
            return false, ""
        }
    }
}

Parser.go

语法分析实现

package interpreter

import (
    "errors"
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
    "strconv"
)

type tSQLParser struct {
    result *ParseResult
}


func newSQLParser() ISQLParser {
    return &tSQLParser{
        newParseResult(),
    }
}

func (me *tSQLParser) Parse(sql string) (error,*ParseResult) {
    lexer := newLexer(sql)
    e, tks := lexer.Parse()
    if e != nil {
        return e, nil
    }

    queue := newTokenQueue(tks)

    // select (* | field-list)
    e = me.ParseSelectPart(queue)
    if e != nil {
        return e, nil
    }

    // from
    e = me.ParseFromPart(queue)
    if e != nil {
        return e, nil
    }

    // where + bool_expression
    e = me.ParseWherePart(queue)
    if e != nil {
        return e, nil
    }

    // eof
    if !queue.IsEmpty() {
        _,t := queue.Poll()
        return errors.New(fmt.Sprintf("expecting EOF at %v", t.from)), nil
    }

    return nil, me.result
}

func (me *tSQLParser) ParseSelectPart(queue *tTokenQueue) error {
    b,v := queue.Poll()
    if !b {
        return errors.New("expecting SELECT keyword")
    }

    if v.token != tokens.Select {
        return errors.New(fmt.Sprintf("expecting SELECT keyword but found '%s'", v.value))
    }

    fields := make([]string, 0)

    b,v = queue.Peek()
    if !b {
        return errors.New("unexpected EOF")
    }

    switch v.token {
    case tokens.Star:
        queue.Poll()
        fields = append(fields, v.value)
        break

    case tokens.Identifier:
        queue.Poll()
        fields = append(fields, v.value)
        break

    default:
        return errors.New(fmt.Sprintf("expecting column name but found '%s'", v.value))
    }

    for {
        b,v := queue.Peek()
        if !b {
            break
        }

        if v.token != tokens.Comma {
            break
        }
        queue.Poll()

        b,v = queue.Peek()
        if !b || v.token != tokens.Identifier {
            return errors.New(fmt.Sprintf("expecting column name but found '%s'", v.value))
        }
        queue.Poll()
        fields = append(fields, v.value)
    }

    if len(fields) > 0 {
        me.result.Fields = fields
        return nil
    }

    return errors.New("expecting column names")
}


func (me *tSQLParser) ParseFromPart(queue *tTokenQueue) error {
    b,v1 := queue.Poll()
    if !b {
        return errors.New("expecting 'from', but eof")
    }

    if v1.token != tokens.From {
        return errors.New(fmt.Sprintf("expecting 'from' at %v", v1.from))
    }

    b,v2 := queue.Poll()
    if !b {
        return errors.New("expecting table name, but eof")
    }
    if v2.token == tokens.Identifier {
        me.result.Table = v2.value
        return nil
    }

    return errors.New(fmt.Sprintf("expecting table name at %v", v2.from))
}


func (me *tSQLParser) ParseWherePart(queue *tTokenQueue) error {
    if queue.IsEmpty() {
        // no where clause
        me.result.RowFilter = newEmptyRowFilter(true)
        return nil
    }

    _,v1 := queue.Poll()
    if v1.token != tokens.Where {
        return errors.New(fmt.Sprintf("expecting 'where' keyword at %v", v1.from))
    }

    stack := newArrayStack()
    e, expression := me.ParseWhereExpression(queue, stack)
    if e != nil {
        return e
    }

    me.result.RowFilter = newExpressionRowFilter(expression)
    return nil
}


func (me *tSQLParser) ParseWhereExpression(queue *tTokenQueue, stack *tArrayStack) (error, IBoolExpression) {
    for {
        if queue.IsEmpty() {
            break
        }

        _,t := queue.Poll()
        switch t.token {
        case tokens.LB:
            stack.Push(t)
            break

        case tokens.Identifier:
            stack.Push(t)
            break

        case tokens.Equal:
            stack.Push(t)
            break

        case tokens.NotEqual:
            stack.Push(t)
            break

        case tokens.Like:
            stack.Push(t)
            break

        case tokens.NotLike:
            stack.Push(t)
            break

        case tokens.Greater:
            stack.Push(t)
            break

        case tokens.GreaterEqual:
            stack.Push(t)
            break

        case tokens.Less:
            stack.Push(t)
            break

        case tokens.LessEqual:
            stack.Push(t)
            break

        case tokens.And:
            stack.Push(t)
            break

        case tokens.OR:
            stack.Push(t)
            break

        case tokens.StringLiteral:
            // field op string
            b, v := stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting operator before %s", t.value)), nil
            }

            op := v.(*tTokenNode)
            if !me.TokenIn(op.token, tokens.Equal, tokens.NotEqual, tokens.Like, tokens.NotLike) {
                return errors.New(fmt.Sprintf("expecting string operator before %s", t.value)), nil
            }

            b,v = stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting column name before %s", op.from)), nil
            }
            field := v.(*tTokenNode)
            if field.token != tokens.Identifier {
                return errors.New(fmt.Sprintf("expecting column name at %v", field.from)), nil
            }

            exp := newStringFieldExpression(op.token, field.value, t.value)
            e := me.PushExpression(exp, stack)
            if e != nil {
                return e, nil
            }
            break


        case tokens.IntLiteral:
            // field op int
            b, v := stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting operator before %s", t.value)), nil
            }

            op := v.(*tTokenNode)
            if !me.TokenIn(op.token, tokens.Equal, tokens.NotEqual, tokens.Greater, tokens.GreaterEqual, tokens.Less, tokens.LessEqual) {
                return errors.New(fmt.Sprintf("expecting int operator before %s", t.value)), nil
            }

            b,v = stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting column name before %v", op.from)), nil
            }
            field := v.(*tTokenNode)
            if field.token != tokens.Identifier {
                return errors.New(fmt.Sprintf("expecting column name at %v", field.from)), nil
            }

            i,_ := strconv.Atoi(t.value)
            exp := newIntFieldExpression(op.token, field.value, i)
            e := me.PushExpression(exp, stack)
            if e != nil {
                return e, nil
            }
            break


        case tokens.RB:
            // )
            b,v := stack.Pop()
            if !b || !v.IsExpression() {
                return errors.New(fmt.Sprintf("expecting expression before %v", t.from)), nil
            }
            expression := v.(*tExpressionNode).Expression

            b,v = stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expected ( not found at %v", t.from)), nil
            }
            lb := v.(*tTokenNode)
            if lb.token != tokens.LB {
                return errors.New(fmt.Sprintf("expected ( not found at %v", t.from)), nil
            }

            e := me.PushExpression(expression, stack)
            if e != nil {
                return e, nil
            }
            break
        }
    }


    if stack.Size() != 1{
        return errors.New("invalid expression"), nil
    }
    ok, node := stack.Peek()
    if !ok || !node.IsExpression() {
        return errors.New("invalid expression"), nil
    }

    return nil,node.(*tExpressionNode).Expression
}


func (me *tSQLParser) TokenIn(t tokens.Tokens, args... tokens.Tokens) bool {
    for _,it := range args {
        if it == t {
            return true
        }
    }
    return false
}


func (me *tSQLParser) PushExpression(exp IBoolExpression, stack *tArrayStack) error {
    b,n := stack.Peek()
    if !b {
        stack.Push(newExpressionNode(exp))
        return nil
    }

    if !n.IsToken() {
        return errors.New("expecting and/or/(")
    }

    t := n.(*tTokenNode)
    if !me.TokenIn(t.token, tokens.And, tokens.OR, tokens.LB) {
        return errors.New("expecting and/or/(")
    }

    if me.TokenIn(t.token, tokens.And, tokens.OR) {
        stack.Pop()
        b,n = stack.Pop()
        if !b || !n.IsExpression(){
            return errors.New("expecting bool expression")
        }
        e := n.(*tExpressionNode)
        return me.PushExpression(newLogicNode(t.token, e.Expression, exp), stack)

    } else {
        stack.Push(newExpressionNode(exp))
        return nil
    }
}

tTokenQueue.go

词法节点队列

package interpreter

type tTokenQueue struct {
    items []*tTokenNode
    size int
    next int
}


func newTokenQueue(nodes []*tTokenNode) *tTokenQueue {
    return &tTokenQueue{
        nodes,
        len(nodes),
        0,
    }
}

func (me *tTokenQueue) Poll() (bool,*tTokenNode) {
    b,v := me.Peek()
    if b {
        me.next++
    }
    return b,v
}


func (me *tTokenQueue) Peek() (bool, *tTokenNode) {
    if me.next >= me.size {
        return false, nil
    }

    it := me.items[me.next]
    return true, it
}

func (me *tTokenQueue) IsEmpty() bool {
    return me.next >= me.size
}

tArrayStack.go

基于数组实现的LIFO堆栈

package interpreter

import "learning/gooop/behavioral_patterns/interpreter/tokens"

type tArrayStack struct {
    items []iStackNode
}


type iStackNode interface {
    IsToken() bool
    IsExpression() bool
}


func newArrayStack() *tArrayStack {
    return &tArrayStack{
        make([]iStackNode, 0),
    }
}

func (me *tArrayStack) Push(node iStackNode) {
    me.items = append(me.items, node)
}

func (me *tArrayStack) Size() int {
    return len(me.items)
}

func (me *tArrayStack) Peek() (bool,iStackNode) {
    if me.Size() <= 0 {
        return false, nil
    }
    return true, me.items[me.Size() - 1]
}

func (me *tArrayStack) Pop() (bool, iStackNode) {
    if me.Size() <= 0 {
        return false, nil
    }

    it := me.items[me.Size() - 1]
    me.items = me.items[:me.Size() - 1]
    return true, it
}

type tTokenNode struct {
    token tokens.Tokens
    value string
    from int
    to int
}

func newTokenNode(t tokens.Tokens, v string, from int, to int) *tTokenNode {
    return &tTokenNode{
        t,v, from, to,
    }
}


func (me *tTokenNode) IsToken() bool {
    return true
}

func (me *tTokenNode) IsExpression() bool {
    return false
}


type tExpressionNode struct {
    Expression IBoolExpression
}


func newExpressionNode(e IBoolExpression) *tExpressionNode {
    return &tExpressionNode{
        e,
    }
}


func (me *tExpressionNode) IsToken() bool {
    return false
}

func (me *tExpressionNode) IsExpression() bool {
    return true
}

IBoolExpression.go

布尔表达式接口

package interpreter

type IBoolExpression interface {
    Eval(row IDataRow) bool
}

tFieldExpression.go

基于字段计算的布尔表达式

package interpreter

import (
    "errors"
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
    "strings"
)

type tFieldExpression struct {
    op tokens.Tokens
    field string
    stringLiteral string
    intLiteral int
}


func newStringFieldExpression(op tokens.Tokens, field string, s string) *tFieldExpression {
    return &tFieldExpression{
        op, field, strings.Trim(s, "'"), 0,
    }
}

func newIntFieldExpression(op tokens.Tokens, field string, value int) *tFieldExpression {
    return &tFieldExpression{
        op, field, "", value,
    }
}

func (me *tFieldExpression) Eval(row IDataRow) bool {
    e, fld := row.GetField(me.field)
    if e != nil {
        panic(e)
    }

    switch fld.DataType() {
    case StringDataType:
        e,v := fld.GetString()
        if e != nil {
            panic(e)
        }
        return me.EvalString(v)

    case IntDataType:
        e,v := fld.GetInt()
        if e != nil {
            panic(e)
        }
        return me.EvalInt(v)

    default:
        panic(errors.New("unknown data type"))
    }
}

func (me *tFieldExpression) EvalString(value string) bool {
    switch me.op {
    case tokens.Equal:
        return value == me.stringLiteral

    case tokens.NotEqual:
        return value != me.stringLiteral

    case tokens.Like:
        fallthrough
    case tokens.NotLike:
        like := false
        p := strings.HasPrefix(value, "%")
        s := strings.HasPrefix(value, "%")

        if p && s {
            like = strings.Contains(value, me.stringLiteral)
        } else if p {
            like = strings.HasSuffix(value, me.stringLiteral)
        } else if s {
            like = strings.HasPrefix(value, me.stringLiteral)
        } else {
            like = value == me.stringLiteral
        }

        if me.op == tokens.Like {
            return like
        } else {
            return !like
        }
        break

    default:
        panic(errors.New(fmt.Sprintf("unsupported string operation: %s", me.op)))
    }

    return false
}


func (me *tFieldExpression) EvalInt(value int) bool {
    switch me.op {
    case tokens.Equal:
        return value == me.intLiteral

    case tokens.NotEqual:
        return value != me.intLiteral

    case tokens.Greater:
        return value > me.intLiteral

    case tokens.GreaterEqual:
        return value >= me.intLiteral

    case tokens.Less:
        return value < me.intLiteral

    case tokens.LessEqual:
        return value <= me.intLiteral

    default:
        panic(errors.New(fmt.Sprintf("unsupported int operation: %s", me.op)))
    }
}

tLogicExpression.go

基于关系(AND/OR)计算的布尔表达式

package interpreter

import (
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
)

type tLogicExpression struct {
    op tokens.Tokens
    left IBoolExpression
    right IBoolExpression
}


func newLogicNode(op tokens.Tokens, left IBoolExpression, right IBoolExpression) *tLogicExpression {
    return &tLogicExpression{
        op, left, right,
    }
}


func (me *tLogicExpression) Eval(row IDataRow) bool {
    switch me.op {
    case tokens.And:
        return me.left.Eval(row) && me.right.Eval(row)

    case tokens.OR:
        return me.left.Eval(row) || me.right.Eval(row)

    default:
        panic(fmt.Sprintf("unsupported bool operation: %s", me.op))
    }
}

SaleOrder.go

销售订单实体类

package interpreter

import (
    "errors"
    "fmt"
    "strings"
)

type SaleOrder struct {
    id *tIntField
    customer *tStringField
    city *tStringField
    product *tStringField
    quantity *tIntField
}

func NewSaleOrder(id int, customer string, city string, product string, quantity int) *SaleOrder {
    return &SaleOrder{
        id: newIntField("id", id),
        customer: newStringField("customer", customer),
        city: newStringField("city", city),
        product: newStringField("product", product),
        quantity: newIntField("quantity", quantity),
    }
}


func (me *SaleOrder) ID() int {
    return me.id.value
}

func (me *SaleOrder) Customer() string {
    return me.customer.value
}

func (me *SaleOrder) City() string {
    return me.city.value
}

func (me *SaleOrder) Product() string {
    return me.product.value
}

func (me *SaleOrder) Quantity() int {
    return me.quantity.value
}

func (me *SaleOrder) GetField(field string) (error,IDataField) {
    if strings.EqualFold(field, "ID") {
        return nil, me.id
    }

    if strings.EqualFold(field, "Customer") {
        return nil, me.customer
    }

    if strings.EqualFold(field, "City") {
        return nil, me.city
    }

    if strings.EqualFold(field, "Product") {
        return nil, me.product
    }

    if strings.EqualFold(field, "Quantity") {
        return nil, me.quantity
    }

    return errors.New("no such field"), nil
}

func (me *SaleOrder) String() string {
    return fmt.Sprintf("id=%v, customer=%v, city=%v, product=%v, quantity=%v", me.ID(), me.Customer(), me.City(), me.Product(), me.Quantity())
}

ISaleOrderService.go

销售订单服务接口, 继承自IDataTable接口

package interpreter

type ISaleOrderService interface {
    IDataTable
    Save(order *SaleOrder)
}

tMockDatabase.go

虚拟的数据库服务, 实现IDatabase接口

package interpreter

import "errors"

type tMockDatabase struct {
    tables map[string]IDataTable
}

func newMockDatabase() IDatabase {
    return &tMockDatabase{
        tables: make(map[string]IDataTable, 0),
    }
}

func (me *tMockDatabase) Register(table IDataTable) {
    me.tables[table.Name()] = table
}

func (me *tMockDatabase) Query(sql string) (error, []IDataRow) {
    parser := newSQLParser()
    e, result := parser.Parse(sql)
    if e != nil {
        return e, nil
    }

    table, ok := me.tables[result.Table]
    if !ok {
        return errors.New("table not found"), nil
    }

    return nil, table.Filter(result.RowFilter)
}

var MockDatabase = newMockDatabase()

tMockSaleOrderService.go

虚拟的销售订单服务, 实现ISaleOrderService接口

package interpreter

type tMockSaleOrderService struct {
    items map[int]*SaleOrder
}

func (me *tMockSaleOrderService) Save(it *SaleOrder) {
    me.items[it.ID()] = it
}


func (me *tMockSaleOrderService) Name() string {
    return "sale_order"
}

func (me *tMockSaleOrderService) Filter(filter IRowFilter) []IDataRow {
    rows := make([]IDataRow, 0)
    for _,it := range me.items {
        if filter.Filter(it) {
            rows = append(rows, it)
        }
    }
    return rows
}

func newMockSaleOrderService() ISaleOrderService {
    it := &tMockSaleOrderService{
        items: make(map[int]*SaleOrder, 0),
    }
    MockDatabase.Register(it)
    return it
}

var MockSaleOrderService = newMockSaleOrderService()

解释器模式小结

解释器模式的优点
(1)在解释器模式中,由于语法是由很多类表示的,当语法规则更改时,
    只需修改相应的非终结符表达式即可;
    当扩展语法时,只需添加相应的非终结符类即可。
(2)增加了新的解释表达式的方式。
(3)解释器模式对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式。

解释器模式的缺点
(1)解释器模式的每个语法都要产生一个非终结符表达式,当语法规则比较复杂时,
    就会产生大量解释类,引起类膨胀,增加系统维护的难度。
(2)解释器模式采用递归调用方法,
    每个非终结符表达式都只关心与自己有关的表达式,
    每个表达式都需要知道最终的结果,
    因此完整表达式的最终结果是通过从后往前递归调用的方式获取的。
    当完整表达式层级较深时,解释效率下降,
    且出错时调试困难,因为递归迭代的层级太深。
    
(摘自 谭勇德 <>)

(end)

有疑问加站长微信联系(非本文作者)