Go設(shè)計(jì)模式--解釋器模式
大家好,這里是每周都在陪你一起進(jìn)步的網(wǎng)管~!今天繼續(xù)學(xué)習(xí)設(shè)計(jì)模式—解釋器模式
解釋器模式是一種行為設(shè)計(jì)模式,可以用來(lái)在程序里創(chuàng)建針對(duì)一個(gè)特點(diǎn)領(lǐng)域語(yǔ)言的解釋器,用于處理解釋領(lǐng)域語(yǔ)言中的語(yǔ)句。換句話說(shuō),該模式定義了領(lǐng)域語(yǔ)言的抽象語(yǔ)法樹以及用示來(lái)解釋語(yǔ)法樹的解釋器。
模式使用場(chǎng)景
解釋器模式,用于解決需要解釋語(yǔ)言中的句子或表達(dá)式的問(wèn)題。以下是一些可以在 程序中使用解釋器模式的真實(shí)場(chǎng)景:
- 處理配置文件
許多應(yīng)用程序使用配置文件來(lái)指定應(yīng)用程序的行為方式。這些配置文件可以用 YAML 或 JSON 等 DSL 編寫。解釋器可用于解析這些配置文件并以應(yīng)用編程語(yǔ)言對(duì)象的形式向應(yīng)用程序提供配置信息。
- 模板引擎
- 模板引擎處理模板和一組變量以產(chǎn)生輸出。模板是DSL的一個(gè)例子,可以使用Interpreter來(lái)解析和處理模板。
- 數(shù)學(xué)表達(dá)式計(jì)算器
- 數(shù)學(xué)表達(dá)式是我們?nèi)粘6寄芙佑|到的,使用了一種特定領(lǐng)域語(yǔ)言語(yǔ)法書寫語(yǔ)句或者叫表達(dá)式的實(shí)例
- 這些表達(dá)式在程序里可以使用解釋器模式進(jìn)行解析和解釋。例如,計(jì)算器應(yīng)用程序可以使用解釋器來(lái)解析和評(píng)估用戶輸入的數(shù)學(xué)表達(dá)式。
- 自然語(yǔ)言處理
- 在更高級(jí)的情況下,解釋器模式可用于解析和解釋自然語(yǔ)言,不過(guò)這通常會(huì)涉及想機(jī)器學(xué)習(xí)這樣的更復(fù)雜的技術(shù)。
雖然解釋器模式可以用來(lái)解決這些問(wèn)題,但它并不總是最好的解決方案。對(duì)于復(fù)雜的語(yǔ)言,使用特定的解析庫(kù)或工具或其他設(shè)計(jì)模式可能更有效。
下面我們先來(lái)學(xué)習(xí)一下解釋器模式的結(jié)構(gòu)組成,然后再嘗試用代碼自己實(shí)現(xiàn)一個(gè)解釋器。
模式構(gòu)成
解釋器模式中的關(guān)鍵組件有:
- 表達(dá)式接口:表示抽象語(yǔ)法樹的元素并定義解釋表達(dá)式的方法。
- 具體表達(dá)式:實(shí)現(xiàn)表達(dá)式接口的結(jié)構(gòu),表示語(yǔ)言語(yǔ)法的各種規(guī)則或元素。
- 上下文對(duì)象:用于保存解釋過(guò)程中所需的任何必要信息或狀態(tài)。
- Parser 或 Builder:負(fù)責(zé)根據(jù)輸入表達(dá)式構(gòu)建抽象語(yǔ)法樹的組件。
下面是解釋器模式構(gòu)成的UML類圖:
看完解釋器模式的結(jié)構(gòu)組成后,我們接下來(lái)嘗試應(yīng)用解釋器模式,用代碼實(shí)現(xiàn)一個(gè)加法運(yùn)算的解釋器。
實(shí)現(xiàn)解釋器模式
看了上面解釋器的結(jié)構(gòu)組成后我們結(jié)下來(lái)通過(guò)代碼一步步實(shí)現(xiàn)其核心組件來(lái)演示怎么用代碼實(shí)現(xiàn)解釋器模式。
以下是如何在 Go 中實(shí)現(xiàn)解釋器模式的步驟。
- 定義表示抽象語(yǔ)法樹中元素的表達(dá)式接口。
- 創(chuàng)建實(shí)現(xiàn) Expression 接口的具體表達(dá)式結(jié)構(gòu),例如 TerminalExpression 和 NonTerminalExpression。
- 定義一個(gè)上下文結(jié)構(gòu)來(lái)保存解釋過(guò)程中可能需要的任何必要數(shù)據(jù)或狀態(tài)(這一步可選)。
- 創(chuàng)建解析器或構(gòu)建器以根據(jù)輸入表達(dá)式構(gòu)造抽象語(yǔ)法樹。 使用創(chuàng)建的抽象語(yǔ)法樹和上下文解釋表達(dá)式。
這里簡(jiǎn)單實(shí)現(xiàn)一個(gè)加減的運(yùn)算器,我們對(duì)每種運(yùn)算定義對(duì)應(yīng)的Expression對(duì)象,在方法里實(shí)現(xiàn)具體的運(yùn)算規(guī)則,避免所有的運(yùn)算操作放到一個(gè)函數(shù)中,這體現(xiàn)了解釋器模式的核心思想,將語(yǔ)法解析的工作拆分到各個(gè)小類中,以此來(lái)避免大而全的解析類。
我們先按照上面的步驟一,定義數(shù)學(xué)運(yùn)算這一領(lǐng)域語(yǔ)言里表示抽象語(yǔ)法樹中元素的表達(dá)式接口:
type Expression interface {
Interpret() int
}
接下來(lái)創(chuàng)建Expression接口的具體實(shí)現(xiàn)類,在我們的加減法運(yùn)算中需要實(shí)現(xiàn)操作數(shù)、加法、減法對(duì)應(yīng)的實(shí)現(xiàn)類。
"本文使用的完整可運(yùn)行源碼
去公眾號(hào)「網(wǎng)管叨bi叨」發(fā)送【設(shè)計(jì)模式】即可領(lǐng)取"
type NumberExpression struct {
val int
}
// 解釋--返回其整數(shù)值
func (n *NumberExpression) Interpret() int {
return n.val
}
// 加法運(yùn)算
type AdditionExpression struct {
left, right Expression
}
// 解釋--進(jìn)行加法操作
func (n *AdditionExpression) Interpret() int {
return n.left.Interpret() + n.right.Interpret()
}
// 減法運(yùn)算
type SubtractionExpression struct {
left, right Expression
}
// 解釋--進(jìn)行減法運(yùn)算
func (n *SubtractionExpression) Interpret() int {
return n.left.Interpret() - n.right.Interpret()
}
最后我們創(chuàng)建一個(gè)表達(dá)式解析器,它會(huì)根據(jù)輸入表達(dá)式構(gòu)造抽象語(yǔ)法樹,使用創(chuàng)建的抽象語(yǔ)法樹和上下文解釋表達(dá)式。
"本文使用的完整可運(yùn)行源碼
去公眾號(hào)「網(wǎng)管叨bi叨」發(fā)送【設(shè)計(jì)模式】即可領(lǐng)取"
type Parser struct {
exp []string
index int
prev Expression
}
func (p *Parser) Parse(exp string) {
p.exp = strings.Split(exp, " ")
for {
if p.index >= len(p.exp) {
return
}
switch p.exp[p.index] {
case "+":
p.prev = p.newAdditionExpression()
case "-":
p.prev = p.newSubtractionExpression()
default:
p.prev = p.newNumberExpression()
}
}
}
func (p *Parser) newAdditionExpression() Expression {
p.index++
return &AdditionExpression{
left: p.prev,
right: p.newNumberExpression(),
}
}
func (p *Parser) newSubtractionExpression() Expression {
p.index++
return &SubtractionExpression{
left: p.prev,
right: p.newNumberExpression(),
}
}
func (p *Parser) newNumberExpression() Expression {
v, _ := strconv.Atoi(p.exp[p.index])
p.index++
return &NumberExpression{
val: v,
}
}
// 返回Expression實(shí)例
// 調(diào)用Interpret方法會(huì)從右向左遞歸計(jì)算出公式結(jié)果
func (p *Parser) Result() Expression {
return p.prev
}
最后,我們用使用 Parse 把客戶端傳遞過(guò)來(lái)的加減法表達(dá)式解析成抽象語(yǔ)法樹,然后運(yùn)行解釋器計(jì)算加減法表達(dá)式的結(jié)果。
"本文使用的完整可運(yùn)行源碼
去公眾號(hào)「網(wǎng)管叨bi叨」發(fā)送【設(shè)計(jì)模式】即可領(lǐng)取"
func main() {
p := &Parser{}
p.Parse("1 + 3 + 3 + 3 - 3")
res := p.Result().Interpret()
expect := 7
if res != expect {
log.Fatalf("error: expect %d got %d", expect, res)
}
fmt.Printf("expect: %d, got: %d", expect, res)
}
總結(jié)
在程序中使用解釋器模式的目標(biāo)是: 定義特定于領(lǐng)域的語(yǔ)言及其語(yǔ)法,使用 AST(抽象語(yǔ)法樹)表示語(yǔ)言中的表達(dá)式或句子,好讓程序能夠根據(jù)一組規(guī)則或操作解釋或評(píng)估表達(dá)式
最后我們?cè)賮?lái)列舉一下解釋器模式的優(yōu)缺點(diǎn)。 使用解釋器模式的優(yōu)點(diǎn)是:
- 關(guān)注點(diǎn)分離:該模式將解釋邏輯與數(shù)據(jù)表示分開(kāi)。
- 可擴(kuò)展性:可以通過(guò)添加新的表達(dá)式結(jié)構(gòu)輕松地?cái)U(kuò)展模式。
- 可重用性:解釋器模式可以在需要解析或解釋特定領(lǐng)域語(yǔ)言的不同項(xiàng)目或上下文中重用。
使用解釋器模式的缺點(diǎn)是:
- 復(fù)雜性:隨著語(yǔ)法規(guī)則數(shù)量的增加,模式會(huì)變得復(fù)雜。
- 性能:對(duì)于大型表達(dá)式,抽象語(yǔ)法樹的遞歸遍歷可能很慢。