個(gè)人學(xué)習(xí)筆記分享启上,當(dāng)前能力有限,請(qǐng)勿貶低店印,菜鳥(niǎo)互學(xué)冈在,大佬繞道
如有勘誤,歡迎指出和討論按摘,本文后期也會(huì)進(jìn)行修正和補(bǔ)充
前言
在初高中的時(shí)候我們都學(xué)過(guò)語(yǔ)法包券,無(wú)論是漢語(yǔ)還是英語(yǔ)都有自己所規(guī)范的語(yǔ)法(盡管大部分人平時(shí)并不遵守。院峡。兴使。)
比如同桌的你
,按照語(yǔ)法照激,的
表示修飾關(guān)系发魄,你
為中心詞,同桌
為定語(yǔ)
規(guī)則定下來(lái)了俩垃,那么我們可以設(shè)計(jì)一個(gè)程序來(lái)識(shí)別這種語(yǔ)法的語(yǔ)句励幼,比如頭禿的程序猿
,改不完的bug
等等口柳。苹粟。。
再舉個(gè)栗子跃闹,對(duì)于算術(shù)式1+(2*3-4)*5
我們需要做的第一件事就是將其從外向里拆開(kāi)嵌削,拆解過(guò)程如下
- 將
1+(2*3-4)*5
拆為1
和(2*3-4)*5
,計(jì)算其和 - 將
(2*3-4)*5
拆為2*3-4
和5
望艺,計(jì)算乘積 - 將
2*3-4
拆為2*3
和4
苛秕,計(jì)算其差 - 將
2*3
拆為2
和3
,計(jì)算其乘積
然后再對(duì)其進(jìn)行計(jì)算找默,拆解過(guò)程中計(jì)算符號(hào)作為兩個(gè)子句的分隔符
也就是說(shuō)我們先將整個(gè)句子拆解艇劫,再對(duì)子句進(jìn)行拆解,一直如此惩激,直至無(wú)法拆解店煞,再對(duì)每一步的結(jié)果進(jìn)行計(jì)算
我們程序猿使用的編程語(yǔ)言也是如此,機(jī)器并不能識(shí)別Java风钻、Python顷蟀、C等等高級(jí)語(yǔ)言,因此需要編譯器將我們的代碼轉(zhuǎn)換成機(jī)器語(yǔ)言
編譯器只能按照某種規(guī)范去轉(zhuǎn)換骡技,因此我們的代碼也需要準(zhǔn)守相關(guān)的語(yǔ)法
這一過(guò)程在==編譯原理==中有詳盡的介紹衩椒,學(xué)藝不精,在這里不多做解釋
解釋器模式(Interpreter Pattern)提供了評(píng)估語(yǔ)言的語(yǔ)法或表達(dá)式的方式,它屬于行為型模式毛萌。
這種模式實(shí)現(xiàn)了一個(gè)表達(dá)式接口苟弛,該接口解釋一個(gè)特定的上下文。
通常被用在 SQL 解析阁将、符號(hào)處理引擎等膏秫,但在開(kāi)發(fā)中使用場(chǎng)景不多,Java中遇到也可用expression4J 替代做盅。
1.介紹
使用目的:給定一個(gè)語(yǔ)言缤削,定義它的文法表示,并定義一個(gè)解釋器吹榴,這個(gè)解釋器使用該標(biāo)識(shí)來(lái)解釋語(yǔ)言中的句子亭敢。
使用時(shí)機(jī):如果一種特定類型的問(wèn)題發(fā)生的頻率足夠高,那么可能就值得將該問(wèn)題的各個(gè)實(shí)例表述為一個(gè)簡(jiǎn)單語(yǔ)言中的句子图筹。這樣就可以構(gòu)建一個(gè)解釋器帅刀,該解釋器通過(guò)解釋這些句子來(lái)解決該問(wèn)題。
解決問(wèn)題:對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器远剩。
實(shí)現(xiàn)方法:構(gòu)建語(yǔ)法樹(shù)扣溺,定義終結(jié)符與非終結(jié)符。并構(gòu)建環(huán)境類瓜晤,包含解釋器之外的一些全局信息锥余,一般是 HashMap
。
應(yīng)用實(shí)例:編譯器痢掠、運(yùn)算表達(dá)式計(jì)算驱犹。
優(yōu)點(diǎn):
- 可擴(kuò)展性比較好,靈活足画。
- 增加了新的解釋表達(dá)式的方式雄驹。
- 易于實(shí)現(xiàn)簡(jiǎn)單文法。
缺點(diǎn):
- 可利用場(chǎng)景比較少锌云,且可以被已封裝好的組件替代荠医。
- 對(duì)于復(fù)雜的文法比較難維護(hù)吁脱。
- 解釋器模式會(huì)引起類膨脹桑涎,每個(gè)文法都必須添加一個(gè)類。
- 解釋器模式采用遞歸調(diào)用方法兼贡,控制不當(dāng)可能出現(xiàn)內(nèi)存占用過(guò)大甚至溢出攻冷。
使用場(chǎng)景:
- 語(yǔ)言文法較為簡(jiǎn)單,且不苛求執(zhí)行效率的情況下遍希。
- 一些重復(fù)出現(xiàn)的問(wèn)題可以用一種簡(jiǎn)單的語(yǔ)言來(lái)進(jìn)行表達(dá)等曼。
- 一個(gè)簡(jiǎn)單語(yǔ)法需要解釋,并且語(yǔ)言中的句子可以表示為一個(gè)抽象語(yǔ)法樹(shù)的時(shí)候,如 XML 文檔解釋
2.結(jié)構(gòu)
解釋器模式包含以下主要角色
- 抽象表達(dá)式(Abstract Expression)角色:定義解釋器的接口禁谦,約定解釋器的解釋操作胁黑,主要包含解釋方法 interpret()。
- 終結(jié)符表達(dá)式(Terminal Expression)角色:是抽象表達(dá)式的子類州泊,用來(lái)實(shí)現(xiàn)文法中與終結(jié)符相關(guān)的操作丧蘸,文法中的每一個(gè)終結(jié)符都有一個(gè)具體終結(jié)表達(dá)式與之相對(duì)應(yīng)。
- 非終結(jié)符表達(dá)式(Nonterminal Expression)角色:也是抽象表達(dá)式的子類遥皂,用來(lái)實(shí)現(xiàn)文法中與非終結(jié)符相關(guān)的操作力喷,文法中的每條規(guī)則都對(duì)應(yīng)于一個(gè)非終結(jié)符表達(dá)式。
- 環(huán)境(Context)角色:通常包含各個(gè)解釋器需要的數(shù)據(jù)或是公共的功能演训,一般用來(lái)傳遞被所有解釋器共享的數(shù)據(jù)弟孟,后面的解釋器可以從這里獲取這些值。
- 客戶端(Client):主要任務(wù)是將需要分析的句子或表達(dá)式轉(zhuǎn)換成使用解釋器對(duì)象描述的抽象語(yǔ)法樹(shù)样悟,然后調(diào)用解釋器的解釋方法拂募,當(dāng)然也可以通過(guò)環(huán)境角色間接訪問(wèn)解釋器的解釋方法。
3.實(shí)現(xiàn)
模板
//抽象表達(dá)式類
interface AbstractExpression
{
public Object interpret(String info); //解釋方法
}
//終結(jié)符表達(dá)式類
class TerminalExpression implements AbstractExpression
{
public Object interpret(String info)
{
//對(duì)終結(jié)符表達(dá)式的處理
}
}
//非終結(jié)符表達(dá)式類
class NonterminalExpression implements AbstractExpression
{
private AbstractExpression exp1;
private AbstractExpression exp2;
public Object interpret(String info)
{
//非對(duì)終結(jié)符表達(dá)式的處理
}
}
//環(huán)境類
class Context
{
private AbstractExpression exp;
public Context()
{
//數(shù)據(jù)初始化
}
public void operation(String info)
{
//調(diào)用相關(guān)表達(dá)式類的解釋方法
}
}
示例
模擬業(yè)務(wù)如下:
- 限行車牌號(hào)乌奇,需要檢查車牌號(hào)地區(qū)及尾號(hào)
- 現(xiàn)在僅允許武漢的雙號(hào)車通行
package com.company.designPattern.interpreter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class InterpreterPatternDemo {
public static void main(String[] args) {
Context context = new Context();
context.checkAccess("鄂A23333");
context.checkAccess("鄂A88888");
context.checkAccess("鄂C10086");
}
}
//抽象表達(dá)式類
interface Expression {
boolean interpret(String info);
}
//終結(jié)符表達(dá)式類
class TerminalExpression implements Expression {
private Set<String> set = new HashSet<>();
public TerminalExpression(String[] data) {
set.addAll(Arrays.asList(data));
}
public boolean interpret(String info) {
return set.contains(info);
}
}
//非終結(jié)符表達(dá)式類
class AndExpression implements Expression {
private Expression head = null;
private Expression tail = null;
public AndExpression(Expression head, Expression tail) {
this.head = head;
this.tail = tail;
}
public boolean interpret(String info) {
return head.interpret(info.substring(0, 2)) && tail.interpret(info.substring(info.length() - 1));
}
}
//環(huán)境類
class Context {
private Expression checkExpression;
public Context() {
Expression headExpression = new TerminalExpression(new String[]{"鄂A"});
Expression tailExpression = new TerminalExpression(new String[]{"0", "2", "4", "6", "8"});
checkExpression = new AndExpression(headExpression, tailExpression);
}
public void checkAccess(String info) {
if (checkExpression.interpret(info)) {
System.out.println(info + ":允許通行");
} else {
System.out.println(info + ":禁止通行");
}
}
}
運(yùn)行結(jié)果
4.擴(kuò)展使用
實(shí)際開(kāi)發(fā)中没讲,Java
中提供了Expression4J
、MESP(Math Expression String Parser)
和Jep
等組件用以替代大部分情況下的解釋器模式礁苗,功能完善強(qiáng)大且封裝性好
比如使用Jep
進(jìn)行借款利息計(jì)算
package com.company.designPattern.interpreter;
import com.singularsys.jep.Jep;
import com.singularsys.jep.JepException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Scanner;
public class SimpleTest {
public static void main(String[] args) throws JepException {
Jep jep = new Jep();
BigDecimal capital = new BigDecimal(10000).setScale(2, RoundingMode.HALF_UP);
BigDecimal rate = new BigDecimal(0.05).setScale(2, RoundingMode.HALF_UP);
System.out.println("本金:" + capital.doubleValue());
System.out.println("日利率(%):" + rate.doubleValue());
System.out.println();
//定義要計(jì)算的數(shù)據(jù)表達(dá)式
String expression = "本金*(1+利率*0.01)^(時(shí)間*30)";
//給相關(guān)變量賦值
jep.addVariable("本金", capital.doubleValue());
jep.addVariable("利率", rate.doubleValue());
while (true) {
//輸入時(shí)間
System.out.println("請(qǐng)輸入借款時(shí)間(月):");
Scanner input = new Scanner(System.in);//創(chuàng)建一個(gè)鍵盤掃描類對(duì)象
jep.addVariable("時(shí)間", input.nextInt());
//計(jì)算
jep.parse(expression); //解析表達(dá)式
Object accrual = jep.evaluate(); //計(jì)算
BigDecimal total = new BigDecimal(accrual.toString()).setScale(2, RoundingMode.HALF_UP);
System.out.println("總還款:" + total.doubleValue());
System.out.println("總利息:" + total.subtract(capital).doubleValue());
System.out.println();
}
}
}
運(yùn)行結(jié)果
可以看到Jep
已經(jīng)提供了很強(qiáng)大的計(jì)算式解析和計(jì)算功能爬凑,而這個(gè)jar包,僅有400kb试伙。嘁信。。
后記
第三方提供的工具已經(jīng)可以替代很多解釋器的使用場(chǎng)景疏叨,當(dāng)然仍有部分適用的場(chǎng)景潘靖,一切根據(jù)實(shí)際業(yè)務(wù)來(lái)啦
作者:Echo_Ye
WX:Echo_YeZ
Email :echo_yezi@qq.com
個(gè)人站點(diǎn):在搭了在搭了。蚤蔓。卦溢。(右鍵 - 新建文件夾)