0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

实践GoF的23种设计模式:解释器模式

元闰子的邀请 来源:元闰子的邀请 2024-04-01 11:01 次阅读

简介

解释器模式(Interpreter Pattern)应该是 GoF 的 23 种设计模式中使用频率最少的一种了,它的应用场景较为局限。

GoF 对它的定义如下:

Given a language, define a represention for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

从定义可以看出,解释器模式主要运用于简单的语法解析场景,比如简单的领域特定语言(DSL)。举个例子,我们可以使用解析器模式来对“1+2+3-4+1”这样的文本表达式完成解析,并得到最终答案“3”。

解释器模式的整体思想是分而治之,每一个语法规则都使用一个类或者结构体(我们称之为 Rule Struct)来定义,它们相互独立,比如前一个例子中,“+” 和 “-” 都各自定义为一个 Rule Struct。因此,解释器模式的可扩展性很好。

通常,我们还能使用抽象语法树(Abstract Syntax Tree,AST)来直观地表示待解释的表达式,比如“1+2+3-4+1”可以表示成这样:

86dd8eb8-e920-11ee-a297-92fbcf53809c.png

UML 结构

87240654-e920-11ee-a297-92fbcf53809c.png

解释器模式通常有 4 种角色:

Context:解释上下文,包含了解释语法需要的所有信息,它是的生命周期贯穿整个解释过程,是一个全局对象。

AbstractExpression:声明了解释语法的方法,通常只有Interpret(*Context)一个方法。

TerminalExpression:实现了 AbstractExpression 接口,定义了终结表达式的解析逻辑。终结表达式在抽象语法树中作为叶子节点。

NonterminalExpression:实现了 AbstractExpression 接口,定义了非终结表达式的解析逻辑。在抽象语法树中,除了叶子节点,其他节点都是非终结表达式。NonterminalExpression 通常会比 TerminalExpression 更复杂一些。

场景上下文

在简单的分布式应用系统(示例代码工程)中,db 模块用来存储服务注册信息和系统监控数据,它是一个 key-value 数据库。为了更高的易用性,它支持简单的 SQL 查询功能。用户在终端控制台上可以通过 SQL 语句来查询数据库中的数据:

874b975a-e920-11ee-a297-92fbcf53809c.png

简单起见,我们实现的 SQL 固定为select xxx,xxx,xxx from xxx where xxx=xxx;的形式,为此,我们要实现 3 个 TerminalExpression,即SelectExpression、FromExpression和WhereExpression,分别解释 select 语句、from 语句、where 语句;以及 1 个 NonterminalExpression,即CompoundExpression,用来解释整个 SQL 语句。

876b987a-e920-11ee-a297-92fbcf53809c.png

代码实现

//demo/db/sql.go
packagedb

//关键点1:定义Context结构体/类,这里是SqlContext,里面存放解析过程所需的状态和数据,以及结果数据
//SqlContextSQL解析器上下文,保存各个表达式解析的中间结果
//当前只支持基于主键的查询SQL语句
typeSqlContextstruct{
tableNamestring
fields[]string
primaryKeyinterface{}
}

...

//关键点2:定义AbstractExpression接口,这里是SqlExpression,其中Interpret方法以Context作为入参
//SqlExpressionSql表达式抽象接口,每个词、符号和句子都属于表达式
typeSqlExpressioninterface{
Interpret(ctx*SqlContext)error
}

//关键点3:定义TerminalExpression,实现AbstractExpression接口,这里是SelectExpression、FromExpression和WhereExpression
//SelectExpressionselect语句解析逻辑,select关键字后面跟的为field,以,分割,比如selectId,name
typeSelectExpressionstruct{
fieldsstring
}

func(s*SelectExpression)Interpret(ctx*SqlContext)error{
fields:=strings.Split(s.fields,",")
iflen(fields)==0{
returnErrSqlInvalidGrammar
}
//关键点4:在解析过程中将状态或者结果数据存储到Context里面
ctx.SetFields(fields)
returnnil
}

//FromExpressionfrom语句解析逻辑,from关键字后面跟的为表名,比如fromregionTable1
typeFromExpressionstruct{
tableNamestring
}

func(f*FromExpression)Interpret(ctx*SqlContext)error{
iff.tableName==""{
returnErrSqlInvalidGrammar
}
ctx.SetTableName(f.tableName)
returnnil
}

//WhereExpressionwhere语句解析逻辑,where关键字后面跟的是主键过滤条件,比如whereid='1'
typeWhereExpressionstruct{
conditionstring
}

func(w*WhereExpression)Interpret(ctx*SqlContext)error{
vals:=strings.Split(w.condition,"=")
iflen(vals)!=2{
returnErrSqlInvalidGrammar
}
ifstrings.Contains(vals[1],"'"){
ctx.SetPrimaryKey(strings.Trim(vals[1],"'"))
returnnil
}
ifval,err:=strconv.Atoi(vals[1]);err==nil{
ctx.SetPrimaryKey(val)
returnnil
}
returnErrSqlInvalidGrammar
}

//关键点5:实现NonterminalExpression,这里是CompoundExpression,它在解释过程中会引用到TerminalExpression,可以将TerminalExpression作为成员变量,也可以在Interpret方法中直接创建新对象。
//CompoundExpressionSQL语句解释器,SQL固定为selectxxx,xxx,xxxfromxxxwherexxx=xxx;的固定格式
//例子:selectregionIdfromregionTablewhereregionId=1
typeCompoundExpressionstruct{
sqlstring
}

func(c*CompoundExpression)Interpret(ctx*SqlContext)error{
childs:=strings.Split(c.sql,"")
iflen(childs)!=6{
returnErrSqlInvalidGrammar
}
//关键点6:在NonterminalExpression的Interpret方法中,调用TerminalExpression的Interpret方法完成对语句的解释。
fori:=0;i< len(childs); i++ {
        switch strings.ToLower(childs[i]) {
        case "select":
            i++
            express := &SelectExpression{fields: childs[i]}
            if err := express.Interpret(ctx); err != nil {
                return err
            }
        case "from":
            i++
            express := &FromExpression{tableName: childs[i]}
            if err := express.Interpret(ctx); err != nil {
                return err
            }
        case "where":
            i++
            express := &WhereExpression{condition: childs[i]}
            if err := express.Interpret(ctx); err != nil {
                return err
            }
        default:
            return ErrSqlInvalidGrammar
        }
    }
    return nil
}

客户端这么使用:

//demo/db/memory_db.go
packagedb

//memoryDb内存数据库
typememoryDbstruct{
tablessync.Map//key为tableName,value为table
}

...

func(m*memoryDb)ExecSql(sqlstring)(*SqlResult,error){
ctx:=NewSqlContext()
express:=&CompoundExpression{sql:sql}
iferr:=express.Interpret(ctx);err!=nil{
returnnil,ErrSqlInvalidGrammar
}
//关键点7:解释成功后,从Context中获取解释结果信息
table,ok:=m.tables.Load(ctx.TableName())
if!ok{
returnnil,ErrTableNotExist
}
record,ok:=table.(*Table).records[ctx.PrimaryKey()]
if!ok{
returnnil,ErrRecordNotFound
}
result:=NewSqlResult()
for_,f:=rangectx.Fields(){
field:=strings.ToLower(f)
ifidx,ok:=table.(*Table).metadata[field];ok{
result.Add(field,record.values[idx])
}
}
returnresult,nil
}

总结实现解释器模式的几个关键点:

定义 Context 结构体/类,这里是SqlContext,里面存放解释过程所需的状态和数据,也会存储解释结果。

定义 AbstractExpression 接口,这里是SqlExpression,其中Interpret方法以 Context 作为入参。

定义 TerminalExpression 结构体,并实现 AbstractExpression 接口,这里是SelectExpression、FromExpression和WhereExpression。

将Interpret方法解释过程中产生的过程状态、数据存储在 Context 上,使得其他 Expression 在解释过程中能够访问。

实现 NonterminalExpression,这里是CompoundExpression,它在解释过程中会引用到 TerminalExpression,可以把 TerminalExpression 作为成员变量,也可以在 Interpret 方法中直接创建新对象。

在 NonterminalExpression 的 Interpret 方法中,调用 TerminalExpression 的 Interpret 方法完成对语句的解释。这里是CompoundExpression.Interpret调用SelectExpression.Interpret、FromExpression.Interpret和WhereExpression.Interpret完成对 SQL 的解释。

解释成功后,从 Context 中获取解释结果。

扩展

领域特定语言 DSL

在前文介绍解释器模式时有提到,它常用于对领域特定语言 DSL 的解释场景,那么什么是 DSL 呢?下面我们将简单介绍一下。

维基百科对 DSL 的定义如下:

Adomain-specific language(DSL) is a computer language specialized to a particular application domain.

可见,DSL 是针对特定领域的一种计算机语言,与之相对的是 GPL,General Purpose Language,即通用编程语言。我们常用的 C/C++Java,Go 等都属于 GPL 的范畴。

DSL 又可细分成 2 类:

External DSL:此类 DSL 拥有独立的语法以及解释器,比如 CSS 用于定义 Web 网页的样式和布局、SQL 用于数据查询、XML 和 YAML 用于配置管理,它们都是典型的 External DSL。

#ExternalDSL举例,SQL
selectid,namefromregionswhereid=‘1’;

Internal DSL:此类 DSL 构建与 GPL 之上,比如流式接口 fluent interface、单元测试中的 Mock 库,它们可以提升 GPL 的易用性和易理解性。

//InternalDSL,Java中的Mockito库
Mockito.when(mockDemo.isTrue()).thenReturn(1);

Martin Fowler 大神专门写了一本书《领域特定语言》来介绍 DSL,更多详细、专业的知识请移步这里。

典型应用场景

简单的语法解析。解释器模式的运用场景较为单一,主要运用于简单的语法解析场景,比如简单的领域特定语言(DSL)。

优缺点

优点

易于扩展。前文提到,使用解释器模式进行语法解释时,每种语法规则都会有对应的 Expression 结构体/类。因此,新增一种语法规则会非常的容易;类似地,改变一种已有的语法规则的解释方式也是很容易,单点改动即可。

缺点

不适用于复杂的语法解释。当语法过于复杂时,Expression 结构体/类的数量将会变得很多,从而难以维护。

与其他模式的关联

解释器模式通常与组合模式(Composite Pattern)结合在一起使用,UML 结构图中的 NonterminalExpression 和 AbstractExpression 的就是组合关系。

另外,解释器模式这种分而治之的方法,与状态模式(State Pattern)中每种状态处理各种的逻辑很是类似。



审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • DSL
    DSL
    +关注

    关注

    2

    文章

    58

    浏览量

    38267
  • 数据存储
    +关注

    关注

    5

    文章

    963

    浏览量

    50854
  • SQL
    SQL
    +关注

    关注

    1

    文章

    759

    浏览量

    44060
  • UML
    UML
    +关注

    关注

    0

    文章

    122

    浏览量

    30848
  • 解释器
    +关注

    关注

    0

    文章

    103

    浏览量

    6493

原文标题:【Go实现】实践GoF的23种设计模式:解释器模式

文章出处:【微信号:yuanrunzi,微信公众号:元闰子的邀请】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    关于国产MCU GOF32F103C8T6 软硬件通用

    +105℃的扩展温度范围,一系列的省电模式保证低功耗应用的要求。GOF32F103X8和GOF32F103XB标准型系列产品提供包括从36脚至100脚的4不同封装形式:根据不同的封装
    发表于 04-19 09:50

    23基本的设计模式总结

    一样。​提到设计模式,不得不感谢GoF(***,四人组),他们1995年出版的《设计模式》一书,第一次将设计模式提升到理论高度,并将之规范化。书中一共总结了
    发表于 03-01 06:08

    Command模式与动态语言

    Gof的设计模式中,有一个模式引起的争议比较大,有很多人甚至认为这个模式应该排除在OO模式之外,原因在于它不具有OO的特性
    发表于 06-22 10:20 988次阅读
    Command<b class='flag-5'>模式</b>与动态语言

    C#23设计模式【完整】

    C#23设计模式
    发表于 08-21 17:38 71次下载

    Java的23设计模式详细资料说明

    Java的23设计模式详细资料说明。
    发表于 12-13 16:28 11次下载
    Java的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>详细资料说明

    通俗易懂的23设计模式解释

    和肯德基就是生产鸡翅的 Factory 工厂模式:客户类和工厂类分开。 消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。 02 建造者模式 MM 最爱
    的头像 发表于 03-09 13:52 1593次阅读

    23java设计模式

    JAVA的设计模式经前人总结可以分为23 设计模式根据使用类型可以分为三: 1、创建模式
    发表于 09-23 15:17 1次下载

    GoF设计模式之访问者模式

    访问者模式的目的是,解耦数据结构和算法,使得系统能够在不改变现有代码结构的基础上,为对象新增一新的操作。
    的头像 发表于 10-08 11:05 663次阅读

    设计模式最佳实践探索—策略模式

    根据不同的应用场景与意图,设计模式主要分为创建型模式、结构型模式和行为型模式三类。本文主要探索行为型模式中的策略
    的头像 发表于 10-31 14:24 916次阅读

    嵌入式软件的设计模式(上)

    一般常见的是四人帮模式GOF23设计模式,是偏向于可复用的面向对象的软件,并不能很完美的契合嵌入式软件,因为嵌入式C语言是结构化的语言
    的头像 发表于 01-20 11:32 1280次阅读
    嵌入式软件的设计<b class='flag-5'>模式</b>(上)

    实践GoF23设计模式:命令模式简介

    因此,我们需要对请求进行抽象,将上下文信息封装到请求对象里,这其实就是命令模式,而该请求对象就是 Command。
    的头像 发表于 01-13 16:36 725次阅读

    设计模式解释设计模式

    Java解释是一将Java程序翻译成机器可执行代码的工具。
    的头像 发表于 06-06 11:22 787次阅读

    实践GoF23设计模式:备忘录模式

    相对于代理模式、工厂模式等设计模式,备忘录模式(Memento)在我们日常开发中出镜率并不高,除了应用场景的限制之外,另一个原因,可能是备忘录模式
    的头像 发表于 11-25 09:05 517次阅读
    <b class='flag-5'>实践</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>:备忘录<b class='flag-5'>模式</b>

    实践GoF23设计模式:适配器模式

    适配器模式所做的就是将一个接口 Adaptee,通过适配器 Adapter 转换成 Client 所期望的另一个接口 Target 来使用,实现原理也很简单,就是 Adapter 通过实现 Target接口,并在对应的方法中调用 Adaptee 的接口实现。
    的头像 发表于 12-10 14:00 471次阅读
    <b class='flag-5'>实践</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>:适配器<b class='flag-5'>模式</b>

    实践GoF23设计模式实现:桥接模式

    也即,将抽象部分和实现部分进行解耦,使得它们能够各自往独立的方向变化。
    的头像 发表于 04-14 09:30 405次阅读
    <b class='flag-5'>实践</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>实现:桥接<b class='flag-5'>模式</b>