簡介
解釋器模式(Interpreter Pattern)應(yīng)該是 GoF 的 23 種設(shè)計模式中使用頻率最少的一種了,它的應(yīng)用場景較為局限。
GoF 對它的定義如下:
Given a language, define a represention for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
從定義可以看出,解釋器模式主要運用于簡單的語法解析場景,比如簡單的領(lǐng)域特定語言(DSL)。舉個例子,我們可以使用解析器模式來對“1+2+3-4+1”這樣的文本表達(dá)式完成解析,并得到最終答案“3”。
解釋器模式的整體思想是分而治之,每一個語法規(guī)則都使用一個類或者結(jié)構(gòu)體(我們稱之為 Rule Struct)來定義,它們相互獨立,比如前一個例子中,“+” 和 “-” 都各自定義為一個 Rule Struct。因此,解釋器模式的可擴(kuò)展性很好。
通常,我們還能使用抽象語法樹(Abstract Syntax Tree,AST)來直觀地表示待解釋的表達(dá)式,比如“1+2+3-4+1”可以表示成這樣:
UML 結(jié)構(gòu)
解釋器模式通常有 4 種角色:
Context:解釋上下文,包含了解釋語法需要的所有信息,它是的生命周期貫穿整個解釋過程,是一個全局對象。
AbstractExpression:聲明了解釋語法的方法,通常只有Interpret(*Context)一個方法。
TerminalExpression:實現(xiàn)了 AbstractExpression 接口,定義了終結(jié)表達(dá)式的解析邏輯。終結(jié)表達(dá)式在抽象語法樹中作為葉子節(jié)點。
NonterminalExpression:實現(xiàn)了 AbstractExpression 接口,定義了非終結(jié)表達(dá)式的解析邏輯。在抽象語法樹中,除了葉子節(jié)點,其他節(jié)點都是非終結(jié)表達(dá)式。NonterminalExpression 通常會比 TerminalExpression 更復(fù)雜一些。
場景上下文
在簡單的分布式應(yīng)用系統(tǒng)(示例代碼工程)中,db 模塊用來存儲服務(wù)注冊信息和系統(tǒng)監(jiān)控數(shù)據(jù),它是一個 key-value 數(shù)據(jù)庫。為了更高的易用性,它支持簡單的 SQL 查詢功能。用戶在終端控制臺上可以通過 SQL 語句來查詢數(shù)據(jù)庫中的數(shù)據(jù):
簡單起見,我們實現(xiàn)的 SQL 固定為select xxx,xxx,xxx from xxx where xxx=xxx;的形式,為此,我們要實現(xiàn) 3 個 TerminalExpression,即SelectExpression、FromExpression和WhereExpression,分別解釋 select 語句、from 語句、where 語句;以及 1 個 NonterminalExpression,即CompoundExpression,用來解釋整個 SQL 語句。
代碼實現(xiàn)
//demo/db/sql.go packagedb //關(guān)鍵點1:定義Context結(jié)構(gòu)體/類,這里是SqlContext,里面存放解析過程所需的狀態(tài)和數(shù)據(jù),以及結(jié)果數(shù)據(jù) //SqlContextSQL解析器上下文,保存各個表達(dá)式解析的中間結(jié)果 //當(dāng)前只支持基于主鍵的查詢SQL語句 typeSqlContextstruct{ tableNamestring fields[]string primaryKeyinterface{} } ... //關(guān)鍵點2:定義AbstractExpression接口,這里是SqlExpression,其中Interpret方法以Context作為入?yún)?//SqlExpressionSql表達(dá)式抽象接口,每個詞、符號和句子都屬于表達(dá)式 typeSqlExpressioninterface{ Interpret(ctx*SqlContext)error } //關(guān)鍵點3:定義TerminalExpression,實現(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)鍵點4:在解析過程中將狀態(tài)或者結(jié)果數(shù)據(jù)存儲到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)鍵點5:實現(xiàn)NonterminalExpression,這里是CompoundExpression,它在解釋過程中會引用到TerminalExpression,可以將TerminalExpression作為成員變量,也可以在Interpret方法中直接創(chuàng)建新對象。 //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)鍵點6:在NonterminalExpression的Interpret方法中,調(diào)用TerminalExpression的Interpret方法完成對語句的解釋。 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)鍵點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é)實現(xiàn)解釋器模式的幾個關(guān)鍵點:
定義 Context 結(jié)構(gòu)體/類,這里是SqlContext,里面存放解釋過程所需的狀態(tài)和數(shù)據(jù),也會存儲解釋結(jié)果。
定義 AbstractExpression 接口,這里是SqlExpression,其中Interpret方法以 Context 作為入?yún)ⅰ?/p>
定義 TerminalExpression 結(jié)構(gòu)體,并實現(xiàn) AbstractExpression 接口,這里是SelectExpression、FromExpression和WhereExpression。
將Interpret方法解釋過程中產(chǎn)生的過程狀態(tài)、數(shù)據(jù)存儲在 Context 上,使得其他 Expression 在解釋過程中能夠訪問。
實現(xiàn) NonterminalExpression,這里是CompoundExpression,它在解釋過程中會引用到 TerminalExpression,可以把 TerminalExpression 作為成員變量,也可以在 Interpret 方法中直接創(chuàng)建新對象。
在 NonterminalExpression 的 Interpret 方法中,調(diào)用 TerminalExpression 的 Interpret 方法完成對語句的解釋。這里是CompoundExpression.Interpret調(diào)用SelectExpression.Interpret、FromExpression.Interpret和WhereExpression.Interpret完成對 SQL 的解釋。
解釋成功后,從 Context 中獲取解釋結(jié)果。
擴(kuò)展
領(lǐng)域特定語言 DSL
在前文介紹解釋器模式時有提到,它常用于對領(lǐng)域特定語言 DSL 的解釋場景,那么什么是 DSL 呢?下面我們將簡單介紹一下。
維基百科對 DSL 的定義如下:
Adomain-specific language(DSL) is a computer language specialized to a particular application domain.
可見,DSL 是針對特定領(lǐng)域的一種計算機語言,與之相對的是 GPL,General Purpose Language,即通用編程語言。我們常用的 C/C++,Java,Go 等都屬于 GPL 的范疇。
DSL 又可細(xì)分成 2 類:
External DSL:此類 DSL 擁有獨立的語法以及解釋器,比如 CSS 用于定義 Web 網(wǎng)頁的樣式和布局、SQL 用于數(shù)據(jù)查詢、XML 和 YAML 用于配置管理,它們都是典型的 External DSL。
#ExternalDSL舉例,SQL selectid,namefromregionswhereid=‘1’;
Internal DSL:此類 DSL 構(gòu)建與 GPL 之上,比如流式接口 fluent interface、單元測試中的 Mock 庫,它們可以提升 GPL 的易用性和易理解性。
//InternalDSL,Java中的Mockito庫 Mockito.when(mockDemo.isTrue()).thenReturn(1);
Martin Fowler 大神專門寫了一本書《領(lǐng)域特定語言》來介紹 DSL,更多詳細(xì)、專業(yè)的知識請移步這里。
典型應(yīng)用場景
簡單的語法解析。解釋器模式的運用場景較為單一,主要運用于簡單的語法解析場景,比如簡單的領(lǐng)域特定語言(DSL)。
優(yōu)缺點
優(yōu)點
易于擴(kuò)展。前文提到,使用解釋器模式進(jìn)行語法解釋時,每種語法規(guī)則都會有對應(yīng)的 Expression 結(jié)構(gòu)體/類。因此,新增一種語法規(guī)則會非常的容易;類似地,改變一種已有的語法規(guī)則的解釋方式也是很容易,單點改動即可。
缺點
不適用于復(fù)雜的語法解釋。當(dāng)語法過于復(fù)雜時,Expression 結(jié)構(gòu)體/類的數(shù)量將會變得很多,從而難以維護(hù)。
與其他模式的關(guān)聯(lián)
解釋器模式通常與組合模式(Composite Pattern)結(jié)合在一起使用,UML 結(jié)構(gòu)圖中的 NonterminalExpression 和 AbstractExpression 的就是組合關(guān)系。
另外,解釋器模式這種分而治之的方法,與狀態(tài)模式(State Pattern)中每種狀態(tài)處理各種的邏輯很是類似。
審核編輯:劉清
-
DSL
+關(guān)注
關(guān)注
2文章
58瀏覽量
38260 -
數(shù)據(jù)存儲
+關(guān)注
關(guān)注
5文章
959瀏覽量
50832 -
SQL
+關(guān)注
關(guān)注
1文章
753瀏覽量
44031 -
UML
+關(guān)注
關(guān)注
0文章
122瀏覽量
30839 -
解釋器
+關(guān)注
關(guān)注
0文章
103瀏覽量
6488
原文標(biāo)題:【Go實現(xiàn)】實踐GoF的23種設(shè)計模式:解釋器模式
文章出處:【微信號:yuanrunzi,微信公眾號:元閏子的邀請】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論