golang设计模式——解析器模式

解释器模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWyCfWkX-1661306052618)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220823162813537.png)]

解释器模式可以描述如何构建一个简单的“语言”解释器。这个模式只在一些特定的领域才有可能用到,如编译器、规则引擎、正则表达式等。好在解释器模式比较简单,大家可以了解一下。

解释器模式

解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

UML

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7XTpuZJ-1661306052619)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220823164815845.png)]

分析

通过定义可以看出,“语言”必然存在规则,而且规则可能相对复杂。对于每一条规则,可以设置对应的类来翻译。

模式定义与UML完全对应,解释器Expression用于翻译Context,由于Context包含内容类型不同,对这些不同类型的内容创建不同解释器进行解释。

举个例子,假设定义一个新的加减乘除语言,对于 8 + 2 - 5这种情况,新的语言为 8 2 5 + -。在这个例子中,Context就是8 2 5 + -,新语言包含数字和运算符,我们就可以创建数字解释器和运算符解释器,对数字和运算符进行解释。

为什么要用解释器模式呢?

主要是为了将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。

如果打算用解释器模式,一般的做法是,将语法规则拆分成一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。

应用场景

解释器模式一般用于编译器、规则引擎、正则表达式,这些功能实现起来都比较麻烦,就不实现它们了。

在实际生活中,我对于中文、英文、编程语言都有一些了解,所以能够解释、理解它们。但对于音乐、舞蹈,根本不知道怎么欣赏。

所以看歌舞表演的时候,特别想有一款翻译器,能对音乐、舞蹈进行翻译,让我了解创作者想表达什么。而且查看解释器源码我也能知道音乐、舞蹈的一般规则,更便于今后的学习。

代码实现

现在让我们开始翻译歌舞剧!

package main

import "fmt"

/**
 * @Description: 内容信息
 */
type Context struct {
   action  string
   content string
}

/**
 * @Description: 翻译接口
 */
type Interpreter interface {
   Interpret(c Context)
}

/**
 * @Description: 翻译音乐
 */
type MusicInterpreter struct {
}

/**
 * @Description: 翻译音乐内容
 * @receiver m
 * @param c
 */
func (m MusicInterpreter) Interpret(c Context) {
   fmt.Println(c.action + " 中 " + c.content + " 的意思是感情高昂")
}

/**
 * @Description: 翻译舞蹈
 */
type DanceInterpreter struct {
}

/**
 * @Description: 翻译舞蹈内容
 * @receiver d
 * @param c
 */
func (d DanceInterpreter) Interpret(c Context) {
   fmt.Println(c.action + " 中 " + c.content + " 的意思是悲凉")
}

func main() {
   cList := []Context{
      {action: "music", content: "高音"},
      {action: "music", content: "低音"},
      {action: "dance", content: "跳跃"},
      {action: "dance", content: "挥手"},
   }
   //对歌舞剧内容进行翻译
   for _, c := range cList {
      if c.action == "music" {
         MusicInterpreter{}.Interpret(c)
      } else if c.action == "dance" {
         DanceInterpreter{}.Interpret(c)
      }
   }
}

输出

➜ myproject go run main.go

music 中 高音 的意思是感情高昂

music 中 低音 的意思是感情高昂

dance 中 跳跃 的意思是悲凉

dance 中 挥手 的意思是悲凉

实例

代码

// Package interpreter 解释器模式
// 采用原课程的示例, 并且做了一下简化
// 假设我们现在有一个监控系统
// 现在需要实现一个告警模块,可以根据输入的告警规则来决定是否触发告警
// 告警规则支持 &&、>、< 3种运算符
// 其中 >、< 优先级比  && 更高
package interpreter

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// AlertRule 告警规则
type AlertRule struct {
	expression IExpression
}

// NewAlertRule NewAlertRule
func NewAlertRule(rule string) (*AlertRule, error) {
	exp, err := NewAndExpression(rule)
	return &AlertRule{expression: exp}, err
}

// Interpret 判断告警是否触发
func (r AlertRule) Interpret(stats map[string]float64) bool {
	return r.expression.Interpret(stats)
}

// IExpression 表达式接口
type IExpression interface {
	Interpret(stats map[string]float64) bool
}

// GreaterExpression >
type GreaterExpression struct {
	key   string
	value float64
}

// Interpret Interpret
func (g GreaterExpression) Interpret(stats map[string]float64) bool {
	v, ok := stats[g.key]
	if !ok {
		return false
	}
	return v > g.value
}

// NewGreaterExpression NewGreaterExpression
func NewGreaterExpression(exp string) (*GreaterExpression, error) {
	data := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(exp), -1)
	if len(data) != 3 || data[1] != ">" {
		return nil, fmt.Errorf("exp is invalid: %s", exp)
	}

	val, err := strconv.ParseFloat(data[2], 10)
	if err != nil {
		return nil, fmt.Errorf("exp is invalid: %s", exp)
	}

	return &GreaterExpression{
		key:   data[0],
		value: val,
	}, nil
}

// LessExpression <
type LessExpression struct {
	key   string
	value float64
}

// Interpret Interpret
func (g LessExpression) Interpret(stats map[string]float64) bool {
	v, ok := stats[g.key]
	if !ok {
		return false
	}
	return v < g.value
}

// NewLessExpression NewLessExpression
func NewLessExpression(exp string) (*LessExpression, error) {
	data := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(exp), -1)
	if len(data) != 3 || data[1] != "<" {
		return nil, fmt.Errorf("exp is invalid: %s", exp)
	}

	val, err := strconv.ParseFloat(data[2], 10)
	if err != nil {
		return nil, fmt.Errorf("exp is invalid: %s", exp)
	}

	return &LessExpression{
		key:   data[0],
		value: val,
	}, nil
}

// AndExpression &&
type AndExpression struct {
	expressions []IExpression
}

// Interpret Interpret
func (e AndExpression) Interpret(stats map[string]float64) bool {
	for _, expression := range e.expressions {
		if !expression.Interpret(stats) {
			return false
		}
	}
	return true
}

// NewAndExpression NewAndExpression
func NewAndExpression(exp string) (*AndExpression, error) {
	exps := strings.Split(exp, "&&")
	expressions := make([]IExpression, len(exps))

	for i, e := range exps {
		var expression IExpression
		var err error

		switch {
		case strings.Contains(e, ">"):
			expression, err = NewGreaterExpression(e)
		case strings.Contains(e, "<"):
			expression, err = NewLessExpression(e)
		default:
			err = fmt.Errorf("exp is invalid: %s", exp)
		}

		if err != nil {
			return nil, err
		}

		expressions[i] = expression
	}

	return &AndExpression{expressions: expressions}, nil
}

测试

package interpreter

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestAlertRule_Interpret(t *testing.T) {
	stats := map[string]float64{
		"a": 1,
		"b": 2,
		"c": 3,
	}
	tests := []struct {
		name  string
		stats map[string]float64
		rule  string
		want  bool
	}{
		{
			name:  "case1",
			stats: stats,
			rule:  "a > 1 && b > 10 && c < 5",
			want:  false,
		},
		{
			name:  "case2",
			stats: stats,
			rule:  "a < 2 && b > 10 && c < 5",
			want:  false,
		},
		{
			name:  "case3",
			stats: stats,
			rule:  "a < 5 && b > 1 && c < 10",
			want:  false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, err := NewAlertRule(tt.rule)
			require.NoError(t, err)
			assert.Equal(t, tt.want, r.Interpret(tt.stats))
		})
	}
}

总结

解释器模式主要是分而治之,将翻译功能分开管理,方便维护。翻译器模式理解难度和使用难度不大,主要是使用场景比较受限。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340