0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

實(shí)踐GoF的23種設(shè)計(jì)模式:解釋器模式

元閏子的邀請(qǐng) ? 來源:元閏子的邀請(qǐng) ? 2024-04-01 11:01 ? 次閱讀

簡(jiǎn)介

解釋器模式(Interpreter Pattern)應(yīng)該是 GoF 的 23 種設(shè)計(jì)模式中使用頻率最少的一種了,它的應(yīng)用場(chǎng)景較為局限。

GoF 對(duì)它的定義如下:

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

從定義可以看出,解釋器模式主要運(yùn)用于簡(jiǎn)單的語法解析場(chǎng)景,比如簡(jiǎn)單的領(lǐng)域特定語言(DSL)。舉個(gè)例子,我們可以使用解析器模式來對(duì)“1+2+3-4+1”這樣的文本表達(dá)式完成解析,并得到最終答案“3”。

解釋器模式的整體思想是分而治之,每一個(gè)語法規(guī)則都使用一個(gè)類或者結(jié)構(gòu)體(我們稱之為 Rule Struct)來定義,它們相互獨(dú)立,比如前一個(gè)例子中,“+” 和 “-” 都各自定義為一個(gè) Rule Struct。因此,解釋器模式的可擴(kuò)展性很好。

通常,我們還能使用抽象語法樹(Abstract Syntax Tree,AST)來直觀地表示待解釋的表達(dá)式,比如“1+2+3-4+1”可以表示成這樣:

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

UML 結(jié)構(gòu)

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

解釋器模式通常有 4 種角色:

Context:解釋上下文,包含了解釋語法需要的所有信息,它是的生命周期貫穿整個(gè)解釋過程,是一個(gè)全局對(duì)象。

AbstractExpression:聲明了解釋語法的方法,通常只有Interpret(*Context)一個(gè)方法。

TerminalExpression:實(shí)現(xiàn)了 AbstractExpression 接口,定義了終結(jié)表達(dá)式的解析邏輯。終結(jié)表達(dá)式在抽象語法樹中作為葉子節(jié)點(diǎn)。

NonterminalExpression:實(shí)現(xiàn)了 AbstractExpression 接口,定義了非終結(jié)表達(dá)式的解析邏輯。在抽象語法樹中,除了葉子節(jié)點(diǎn),其他節(jié)點(diǎn)都是非終結(jié)表達(dá)式。NonterminalExpression 通常會(huì)比 TerminalExpression 更復(fù)雜一些。

場(chǎng)景上下文

在簡(jiǎn)單的分布式應(yīng)用系統(tǒng)(示例代碼工程)中,db 模塊用來存儲(chǔ)服務(wù)注冊(cè)信息和系統(tǒng)監(jiān)控?cái)?shù)據(jù),它是一個(gè) key-value 數(shù)據(jù)庫。為了更高的易用性,它支持簡(jiǎn)單的 SQL 查詢功能。用戶在終端控制臺(tái)上可以通過 SQL 語句來查詢數(shù)據(jù)庫中的數(shù)據(jù):

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

簡(jiǎn)單起見,我們實(shí)現(xiàn)的 SQL 固定為select xxx,xxx,xxx from xxx where xxx=xxx;的形式,為此,我們要實(shí)現(xiàn) 3 個(gè) TerminalExpression,即SelectExpression、FromExpression和WhereExpression,分別解釋 select 語句、from 語句、where 語句;以及 1 個(gè) NonterminalExpression,即CompoundExpression,用來解釋整個(gè) SQL 語句。

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

代碼實(shí)現(xiàn)

//demo/db/sql.go
packagedb

//關(guān)鍵點(diǎn)1:定義Context結(jié)構(gòu)體/類,這里是SqlContext,里面存放解析過程所需的狀態(tài)和數(shù)據(jù),以及結(jié)果數(shù)據(jù)
//SqlContextSQL解析器上下文,保存各個(gè)表達(dá)式解析的中間結(jié)果
//當(dāng)前只支持基于主鍵的查詢SQL語句
typeSqlContextstruct{
tableNamestring
fields[]string
primaryKeyinterface{}
}

...

//關(guān)鍵點(diǎn)2:定義AbstractExpression接口,這里是SqlExpression,其中Interpret方法以Context作為入?yún)?//SqlExpressionSql表達(dá)式抽象接口,每個(gè)詞、符號(hào)和句子都屬于表達(dá)式
typeSqlExpressioninterface{
Interpret(ctx*SqlContext)error
}

//關(guān)鍵點(diǎn)3:定義TerminalExpression,實(shí)現(xiàn)AbstractExpression接口,這里是SelectExpression、FromExpression和WhereExpression
//SelectExpressionselect語句解析邏輯,select關(guān)鍵字后面跟的為field,以,分割,比如selectId,name
typeSelectExpressionstruct{
fieldsstring
}

func(s*SelectExpression)Interpret(ctx*SqlContext)error{
fields:=strings.Split(s.fields,",")
iflen(fields)==0{
returnErrSqlInvalidGrammar
}
//關(guān)鍵點(diǎn)4:在解析過程中將狀態(tài)或者結(jié)果數(shù)據(jù)存儲(chǔ)到Context里面
ctx.SetFields(fields)
returnnil
}

//FromExpressionfrom語句解析邏輯,from關(guān)鍵字后面跟的為表名,比如fromregionTable1
typeFromExpressionstruct{
tableNamestring
}

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

//WhereExpressionwhere語句解析邏輯,where關(guān)鍵字后面跟的是主鍵過濾條件,比如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
}

//關(guān)鍵點(diǎn)5:實(shí)現(xiàn)NonterminalExpression,這里是CompoundExpression,它在解釋過程中會(huì)引用到TerminalExpression,可以將TerminalExpression作為成員變量,也可以在Interpret方法中直接創(chuàng)建新對(duì)象。
//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
}
//關(guān)鍵點(diǎn)6:在NonterminalExpression的Interpret方法中,調(diào)用TerminalExpression的Interpret方法完成對(duì)語句的解釋。
fori:=0;i

客戶端這么使用:

//demo/db/memory_db.go
packagedb

//memoryDb內(nèi)存數(shù)據(jù)庫
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
}
//關(guān)鍵點(diǎn)7:解釋成功后,從Context中獲取解釋結(jié)果信息
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
}

總結(jié)實(shí)現(xiàn)解釋器模式的幾個(gè)關(guān)鍵點(diǎn):

定義 Context 結(jié)構(gòu)體/類,這里是SqlContext,里面存放解釋過程所需的狀態(tài)和數(shù)據(jù),也會(huì)存儲(chǔ)解釋結(jié)果。

定義 AbstractExpression 接口,這里是SqlExpression,其中Interpret方法以 Context 作為入?yún)ⅰ?/p>

定義 TerminalExpression 結(jié)構(gòu)體,并實(shí)現(xiàn) AbstractExpression 接口,這里是SelectExpression、FromExpression和WhereExpression。

將Interpret方法解釋過程中產(chǎn)生的過程狀態(tài)、數(shù)據(jù)存儲(chǔ)在 Context 上,使得其他 Expression 在解釋過程中能夠訪問。

實(shí)現(xiàn) NonterminalExpression,這里是CompoundExpression,它在解釋過程中會(huì)引用到 TerminalExpression,可以把 TerminalExpression 作為成員變量,也可以在 Interpret 方法中直接創(chuàng)建新對(duì)象。

在 NonterminalExpression 的 Interpret 方法中,調(diào)用 TerminalExpression 的 Interpret 方法完成對(duì)語句的解釋。這里是CompoundExpression.Interpret調(diào)用SelectExpression.Interpret、FromExpression.Interpret和WhereExpression.Interpret完成對(duì) SQL 的解釋。

解釋成功后,從 Context 中獲取解釋結(jié)果。

擴(kuò)展

領(lǐng)域特定語言 DSL

在前文介紹解釋器模式時(shí)有提到,它常用于對(duì)領(lǐng)域特定語言 DSL 的解釋場(chǎng)景,那么什么是 DSL 呢?下面我們將簡(jiǎn)單介紹一下。

維基百科對(duì) DSL 的定義如下:

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

可見,DSL 是針對(duì)特定領(lǐng)域的一種計(jì)算機(jī)語言,與之相對(duì)的是 GPL,General Purpose Language,即通用編程語言。我們常用的 C/C++,Java,Go 等都屬于 GPL 的范疇。

DSL 又可細(xì)分成 2 類:

External DSL:此類 DSL 擁有獨(dú)立的語法以及解釋器,比如 CSS 用于定義 Web 網(wǎng)頁的樣式和布局、SQL 用于數(shù)據(jù)查詢、XML 和 YAML 用于配置管理,它們都是典型的 External DSL。

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

Internal DSL:此類 DSL 構(gòu)建與 GPL 之上,比如流式接口 fluent interface、單元測(cè)試中的 Mock 庫,它們可以提升 GPL 的易用性和易理解性。

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

Martin Fowler 大神專門寫了一本書《領(lǐng)域特定語言》來介紹 DSL,更多詳細(xì)、專業(yè)的知識(shí)請(qǐng)移步這里。

典型應(yīng)用場(chǎng)景

簡(jiǎn)單的語法解析。解釋器模式的運(yùn)用場(chǎng)景較為單一,主要運(yùn)用于簡(jiǎn)單的語法解析場(chǎng)景,比如簡(jiǎn)單的領(lǐng)域特定語言(DSL)。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

易于擴(kuò)展。前文提到,使用解釋器模式進(jìn)行語法解釋時(shí),每種語法規(guī)則都會(huì)有對(duì)應(yīng)的 Expression 結(jié)構(gòu)體/類。因此,新增一種語法規(guī)則會(huì)非常的容易;類似地,改變一種已有的語法規(guī)則的解釋方式也是很容易,單點(diǎn)改動(dòng)即可。

缺點(diǎn)

不適用于復(fù)雜的語法解釋。當(dāng)語法過于復(fù)雜時(shí),Expression 結(jié)構(gòu)體/類的數(shù)量將會(huì)變得很多,從而難以維護(hù)。

與其他模式的關(guān)聯(lián)

解釋器模式通常與組合模式(Composite Pattern)結(jié)合在一起使用,UML 結(jié)構(gòu)圖中的 NonterminalExpression 和 AbstractExpression 的就是組合關(guān)系。

另外,解釋器模式這種分而治之的方法,與狀態(tài)模式(State Pattern)中每種狀態(tài)處理各種的邏輯很是類似。



審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • DSL
    DSL
    +關(guān)注

    關(guān)注

    2

    文章

    58

    瀏覽量

    38303
  • 數(shù)據(jù)存儲(chǔ)

    關(guān)注

    5

    文章

    971

    瀏覽量

    50909
  • SQL
    SQL
    +關(guān)注

    關(guān)注

    1

    文章

    764

    瀏覽量

    44134
  • UML
    UML
    +關(guān)注

    關(guān)注

    0

    文章

    122

    瀏覽量

    30861
  • 解釋器
    +關(guān)注

    關(guān)注

    0

    文章

    103

    瀏覽量

    6513

原文標(biāo)題:【Go實(shí)現(xiàn)】實(shí)踐GoF的23種設(shè)計(jì)模式:解釋器模式

文章出處:【微信號(hào):yuanrunzi,微信公眾號(hào):元閏子的邀請(qǐng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    關(guān)于國(guó)產(chǎn)MCU GOF32F103C8T6 軟硬件通用

    +105℃的擴(kuò)展溫度范圍,一系列的省電模式保證低功耗應(yīng)用的要求。GOF32F103X8和GOF32F103XB標(biāo)準(zhǔn)型系列產(chǎn)品提供包括從36腳至100腳的4不同封裝形式:根據(jù)不同的封裝
    發(fā)表于 04-19 09:50

    23基本的設(shè)計(jì)模式總結(jié)

    一樣。?提到設(shè)計(jì)模式,不得不感謝GoF(***,四人組),他們1995年出版的《設(shè)計(jì)模式》一書,第一次將設(shè)計(jì)模式提升到理論高度,并將之規(guī)范化。書中一共總結(jié)了
    發(fā)表于 03-01 06:08

    Command模式與動(dòng)態(tài)語言

    Gof的設(shè)計(jì)模式中,有一個(gè)模式引起的爭(zhēng)議比較大,有很多人甚至認(rèn)為這個(gè)模式應(yīng)該排除在OO模式之外,原因在于它不具有OO的特性
    發(fā)表于 06-22 10:20 ?1013次閱讀
    Command<b class='flag-5'>模式</b>與動(dòng)態(tài)語言

    C#23設(shè)計(jì)模式【完整】

    C#23設(shè)計(jì)模式
    發(fā)表于 08-21 17:38 ?71次下載

    Java的23設(shè)計(jì)模式詳細(xì)資料說明

    Java的23設(shè)計(jì)模式詳細(xì)資料說明。
    發(fā)表于 12-13 16:28 ?11次下載
    Java的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>詳細(xì)資料說明

    通俗易懂的23設(shè)計(jì)模式解釋

    和肯德基就是生產(chǎn)雞翅的 Factory 工廠模式:客戶類和工廠類分開。 消費(fèi)者任何時(shí)候需要某種產(chǎn)品,只需向工廠請(qǐng)求即可。消費(fèi)者無須修改就可以接納新產(chǎn)品。缺點(diǎn)是當(dāng)產(chǎn)品修改時(shí),工廠類也要做相應(yīng)的修改。如:如何創(chuàng)建及如何向客戶端提供。 02 建造者模式 MM 最愛
    的頭像 發(fā)表于 03-09 13:52 ?1622次閱讀

    23java設(shè)計(jì)模式

    JAVA的設(shè)計(jì)模式經(jīng)前人總結(jié)可以分為23 設(shè)計(jì)模式根據(jù)使用類型可以分為三: 1、創(chuàng)建模式
    發(fā)表于 09-23 15:17 ?1次下載

    GoF設(shè)計(jì)模式之訪問者模式

    訪問者模式的目的是,解耦數(shù)據(jù)結(jié)構(gòu)和算法,使得系統(tǒng)能夠在不改變現(xiàn)有代碼結(jié)構(gòu)的基礎(chǔ)上,為對(duì)象新增一新的操作。
    的頭像 發(fā)表于 10-08 11:05 ?690次閱讀

    設(shè)計(jì)模式最佳實(shí)踐探索—策略模式

    根據(jù)不同的應(yīng)用場(chǎng)景與意圖,設(shè)計(jì)模式主要分為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式三類。本文主要探索行為型模式中的策略
    的頭像 發(fā)表于 10-31 14:24 ?945次閱讀

    嵌入式軟件的設(shè)計(jì)模式(上)

    一般常見的是四人幫模式GOF23設(shè)計(jì)模式,是偏向于可復(fù)用的面向?qū)ο蟮能浖?,并不能很完美的契合嵌入式軟件,因?yàn)榍度胧紺語言是結(jié)構(gòu)化的語言
    的頭像 發(fā)表于 01-20 11:32 ?1328次閱讀
    嵌入式軟件的設(shè)計(jì)<b class='flag-5'>模式</b>(上)

    實(shí)踐GoF23設(shè)計(jì)模式:命令模式簡(jiǎn)介

    因此,我們需要對(duì)請(qǐng)求進(jìn)行抽象,將上下文信息封裝到請(qǐng)求對(duì)象里,這其實(shí)就是命令模式,而該請(qǐng)求對(duì)象就是 Command。
    的頭像 發(fā)表于 01-13 16:36 ?764次閱讀

    設(shè)計(jì)模式解釋設(shè)計(jì)模式

    Java解釋是一將Java程序翻譯成機(jī)器可執(zhí)行代碼的工具。
    的頭像 發(fā)表于 06-06 11:22 ?854次閱讀

    實(shí)踐GoF23設(shè)計(jì)模式:備忘錄模式

    相對(duì)于代理模式、工廠模式等設(shè)計(jì)模式,備忘錄模式(Memento)在我們?nèi)粘i_發(fā)中出鏡率并不高,除了應(yīng)用場(chǎng)景的限制之外,另一個(gè)原因,可能是備忘錄模式
    的頭像 發(fā)表于 11-25 09:05 ?549次閱讀
    <b class='flag-5'>實(shí)踐</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>:備忘錄<b class='flag-5'>模式</b>

    實(shí)踐GoF23設(shè)計(jì)模式:適配器模式

    適配器模式所做的就是將一個(gè)接口 Adaptee,通過適配器 Adapter 轉(zhuǎn)換成 Client 所期望的另一個(gè)接口 Target 來使用,實(shí)現(xiàn)原理也很簡(jiǎn)單,就是 Adapter 通過實(shí)現(xiàn) Target接口,并在對(duì)應(yīng)的方法中調(diào)用 Adaptee 的接口實(shí)現(xiàn)。
    的頭像 發(fā)表于 12-10 14:00 ?513次閱讀
    <b class='flag-5'>實(shí)踐</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>:適配器<b class='flag-5'>模式</b>

    實(shí)踐GoF23設(shè)計(jì)模式實(shí)現(xiàn):橋接模式

    也即,將抽象部分和實(shí)現(xiàn)部分進(jìn)行解耦,使得它們能夠各自往獨(dú)立的方向變化。
    的頭像 發(fā)表于 04-14 09:30 ?451次閱讀
    <b class='flag-5'>實(shí)踐</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>實(shí)現(xiàn):橋接<b class='flag-5'>模式</b>