Java設(shè)計(jì)模式之java解釋器模式詳解
介紹
解釋器模式(Interpreter Pattern
):定義一個(gè)語言的文法,并且建立一個(gè)解釋器來解釋該語言中的句子,這里的 “語言” 是指使用規(guī)定格式和語法的代碼。解釋器模式是一種類行為型模式。
角色
AbstractExpression(抽象解釋器):在抽象表達(dá)式中聲明了抽象的解釋操作,具體的解釋任務(wù)由各個(gè)實(shí)現(xiàn)類完成,它是所有終結(jié)符表達(dá)式和非終結(jié)符表達(dá)式的公共父類。
TerminalExpression(終結(jié)符表達(dá)式):實(shí)現(xiàn)與文法中的元素相關(guān)聯(lián)的解釋操作,通常一個(gè)解釋器模式中只有一個(gè)終結(jié)表達(dá)式,但有多個(gè)實(shí)例,對(duì)應(yīng)不同的終結(jié)符。
NonterminalExpression(非終結(jié)符表達(dá)式):文法中的每條規(guī)則對(duì)應(yīng)于一個(gè)非終結(jié)表達(dá)式,非終結(jié)符表達(dá)式根據(jù)邏輯的復(fù)雜程度而增加,原則上每個(gè)文法規(guī)則都對(duì)應(yīng)一個(gè)非終結(jié)符表達(dá)式
Context(環(huán)境類):環(huán)境類又稱為上下文類,它用于存儲(chǔ)解釋器之外的一些全局信息,通常它臨時(shí)存儲(chǔ)了需要解釋的語句。
客戶類(Test): 客戶端,解析表達(dá)式,構(gòu)建抽象語法樹,執(zhí)行具體的解釋操作等.
計(jì)算器案例
環(huán)境類,存儲(chǔ)解釋器之外的一些全局信息,通常它臨時(shí)存儲(chǔ)了需要解釋的語句
public class Context { private Map<Expression, Integer> map = new HashMap<>(); //定義變量 public void add(Expression s, Integer value) { map.put(s, value); } //將變量轉(zhuǎn)換成數(shù)字 public int lookup(Expression s){ return map.get(s); } }
解釋器接口
public interface Expression { int interpreter(Context context);//一定會(huì)有解釋方法 }
抽象非終結(jié)符表達(dá)式
public abstract class NonTerminalExpression implements Expression{ Expression e1,e2; public NonTerminalExpression(Expression e1, Expression e2){ this.e1 = e1; this.e2 = e2; } }
減法表達(dá)式實(shí)現(xiàn)類
public class MinusOperation extends NonTerminalExpression { public MinusOperation(Expression e1, Expression e2) { super(e1, e2); } //將兩個(gè)表達(dá)式相減 @Override public int interpreter(Context context) { return this.e1.interpreter(context) - this.e2.interpreter(context); } }
加法表達(dá)式實(shí)現(xiàn)類
public class PlusOperation extends NonTerminalExpression { public PlusOperation(Expression e1, Expression e2) { super(e1, e2); } //將兩個(gè)表達(dá)式相加 @Override public int interpreter(Context context) { return this.e1.interpreter(context) + this.e2.interpreter(context); } }
終結(jié)符表達(dá)式(在這個(gè)例子,用來存放數(shù)字,或者代表數(shù)字的字符)
public class TerminalExpression implements Expression{ String variable; public TerminalExpression(String variable){ this.variable = variable; } //獲得該變量的值 @Override public int interpreter(Context context) { return context.lookup(this); } }
測(cè)試:
public class Test { public static void main(String[] args) { Context context = new Context(); TerminalExpression a = new TerminalExpression("a"); TerminalExpression b = new TerminalExpression("b"); TerminalExpression c = new TerminalExpression("c"); context.add(a, 4); context.add(b, 8); context.add(c, 2); //new PlusOperation(a,b).interpreter(context)--->返回12 // c.interpreter(context)--->2 //MinusOperation(12,2)..interpreter(context)--->10 System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context)); } }
UML圖
深入挖掘
非終結(jié)符表達(dá)式(相當(dāng)于樹的樹杈):在這個(gè)例子中就是相加,相減的表達(dá)式,稱為非終結(jié)符,這是非常形象的,因?yàn)楫?dāng)運(yùn)算遇到這類的表達(dá)式的時(shí)候,必須先把非終結(jié)符的結(jié)果計(jì)算出來,猶如剝繭一般,一層一層的調(diào)用,就比如上面的
new MinusOperation(new PlusOperation(a,b), c).interpreter(context)
這個(gè)MinusOperation
左邊參數(shù)是new PlusOperation(a,b)
,是非終結(jié)符表達(dá)式,所以要調(diào)用PlusOperation,因?yàn)?code>PlusOperation的左右兩邊都是TerminalExpression
,是終結(jié)符表達(dá)式,所以計(jì)算然后返回,到最外面的MinusOperation函數(shù),發(fā)現(xiàn)右邊c是終結(jié)符表達(dá)式,所以可以計(jì)算。
終結(jié)符表達(dá)式(相當(dāng)于樹的葉子):遇到這個(gè)表達(dá)式interpreter執(zhí)行能直接返回結(jié)果,不會(huì)向下繼續(xù)調(diào)用。
構(gòu)建的語法樹
葉子節(jié)點(diǎn)即為終結(jié)符,樹杈即為非終結(jié)符,遇到非終結(jié)符要繼續(xù)往下解析,遇到終結(jié)符則返回。a+b-c(4+8-2)
上面的語法樹是手動(dòng)建的(new MinusOperation(new PlusOperation(a,b), c).interpreter(context))
,實(shí)際情況,客戶輸入的都是(4+8-2)這樣的式子,所以,接下來寫的就是解析的輸入式子然后自動(dòng)構(gòu)建語法樹,然后計(jì)算結(jié)果.
public class Context { private Map<Expression, Integer> map = new HashMap<>(); public void add(Expression s, Integer value){ map.put(s, value); } public Integer lookup(Expression s){ return map.get(s); } //構(gòu)建語法樹的主要方法 public static Expression build(String str) { //主要利用棧來實(shí)現(xiàn) Stack<Expression> objects = new Stack<>(); for (int i = 0; i < str.length(); i++){ char c = str.charAt(i); //遇到運(yùn)算符號(hào)+號(hào)時(shí)候 if (c == '+'){ //先出棧 Expression pop = objects.pop(); //將運(yùn)算結(jié)果入棧 objects.push(new PlusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i))))); } else if (c == '-'){ //遇到減號(hào)類似加號(hào) Expression pop = objects.pop(); objects.push(new MinusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i))))); } else { //遇到非終結(jié)符直接入棧(基本就是第一個(gè)數(shù)字的情況) objects.push(new TerminalExpression(String.valueOf(str.charAt(i)))); } } //把最后的棧頂元素返回 return objects.pop(); } } public class TerminalExpression implements Expression { String variable; public TerminalExpression(String variable){ this.variable = variable; } @Override public int interpreter(Context context) { //因?yàn)橐嫒葜暗陌姹? Integer lookup = context.lookup(this); if (lookup == null) //若在map中能找到對(duì)應(yīng)的數(shù)則返回 return Integer.valueOf(variable); //找不到則直接返回(認(rèn)為輸入的就是數(shù)字) return lookup; } } public class Test { public static void main(String[] args) { Context context = new Context(); TerminalExpression a = new TerminalExpression("a"); TerminalExpression b = new TerminalExpression("b"); TerminalExpression c = new TerminalExpression("c"); String str = "4+8-2+9+9-8"; Expression build = Context.build(str); System.out.println("4+8-2+9+9-8=" + build.interpreter(context)); context.add(a, 4); context.add(b, 8); context.add(c, 2); System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context)); } }
解釋器模式總結(jié)
解釋器模式為自定義語言的設(shè)計(jì)和實(shí)現(xiàn)提供了一種解決方案,它用于定義一組文法規(guī)則并通過這組文法規(guī)則來解釋語言中的句子。雖然解釋器模式的使用頻率不是特別高,但是它在正則表達(dá)式、XML文檔解釋等領(lǐng)域還是得到了廣泛使用。
主要優(yōu)點(diǎn)
- 易于改變和擴(kuò)展文法。由于在解釋器模式中使用類來表示語言的文法規(guī)則,因此可以通過繼承等機(jī)制來改變或擴(kuò)展文法。
- 每一條文法規(guī)則都可以表示為一個(gè)類,因此可以方便地實(shí)現(xiàn)一個(gè)簡(jiǎn)單的語言。
- 實(shí)現(xiàn)文法較為容易。在抽象語法樹中每一個(gè)表達(dá)式節(jié)點(diǎn)類的實(shí)現(xiàn)方式都是相似的,這些類的代碼編寫都不會(huì)特別復(fù)雜,還可以通過一些工具自動(dòng)生成節(jié)點(diǎn)類代碼。
- 增加新的解釋表達(dá)式較為方便。如果用戶需要增加新的解釋表達(dá)式只需要對(duì)應(yīng)增加一個(gè)新的終結(jié)符表達(dá)式或非終結(jié)符表達(dá)式類,原有表達(dá)式類代碼無須修改,符合"開閉原則"。
主要缺點(diǎn)
- 對(duì)于復(fù)雜文法難以維護(hù)。在解釋器模式中,每一條規(guī)則至少需要定義一個(gè)類,因此如果一個(gè)語言包含太多文法規(guī)則,類的個(gè)數(shù)將會(huì)急劇增加,導(dǎo)致系統(tǒng)難以管理和維護(hù),此時(shí)可以考慮使用語法分析程序等方式來取代解釋器模式。
- 執(zhí)行效率較低。由于在解釋器模式中使用了大量的循環(huán)和遞歸調(diào)用,因此在解釋較為復(fù)雜的句子時(shí)其速度很慢,而且代碼的調(diào)試過程也比較麻煩。
適用場(chǎng)景
- 可以將一個(gè)需要解釋執(zhí)行的語言中的句子表示為一個(gè)抽象語法樹。
- 一些重復(fù)出現(xiàn)的問題可以用一種簡(jiǎn)單的語言來進(jìn)行表達(dá)。
- 一個(gè)語言的文法較為簡(jiǎn)單。
- 對(duì)執(zhí)行效率要求不高。
解釋器模式的典型應(yīng)用
Spring EL表達(dá)式中的解釋器模式
在下面的類圖中,Expression
是一個(gè)接口,相當(dāng)于我們解釋器模式中的非終結(jié)符表達(dá)式,而ExpressionParser
相當(dāng)于終結(jié)符表達(dá)式。根據(jù)不同的Parser
對(duì)象,返回不同的Expression
對(duì)象
Expression接口:
//抽象的非終結(jié)符表達(dá)式 public interface Expression { Object getValue() throws EvaluationException; Object getValue(Object rootObject) throws EvaluationException; }
SpelExpression類:
//具體的非終結(jié)符表達(dá)式 public class SpelExpression implements Expression { @Override public Object getValue() throws EvaluationException { Object result; if (this.compiledAst != null) { try { TypedValue contextRoot = evaluationContext == null ? null : evaluationContext.getRootObject(); return this.compiledAst.getValue(contextRoot == null ? null : contextRoot.getValue(), evaluationContext); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { // Running in SpelCompilerMode.immediate mode - propagate exception to caller throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; } }
CompositeStringExpression:
//具體的非終結(jié)符表達(dá)式 public class CompositeStringExpression implements Expression { @Override public String getValue() throws EvaluationException { StringBuilder sb = new StringBuilder(); for (Expression expression : this.expressions) { String value = expression.getValue(String.class); if (value != null) { sb.append(value); } } return sb.toString(); } }
ExpressionParser接口:
public interface ExpressionParser { //解析表達(dá)式 Expression parseExpression(String expressionString) throws ParseException; Expression parseExpression(String expressionString, ParserContext context) throws ParseException; }
TemplateAwareExpressionParser類:
public abstract class TemplateAwareExpressionParser implements ExpressionParser { @Override public Expression parseExpression(String expressionString) throws ParseException { return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT); } //根據(jù)不同的parser返回不同的Expression對(duì)象 @Override public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { if (context == null) { context = NON_TEMPLATE_PARSER_CONTEXT; } if (context.isTemplate()) { return parseTemplate(expressionString, context); } else { return doParseExpression(expressionString, context); } } private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException { if (expressionString.length() == 0) { return new LiteralExpression(""); } Expression[] expressions = parseExpressions(expressionString, context); if (expressions.length == 1) { return expressions[0]; } else { return new CompositeStringExpression(expressionString, expressions); } } //抽象的,由子類去實(shí)現(xiàn) protected abstract Expression doParseExpression(String expressionString, ParserContext context) throws ParseException; }
SpelExpressionParser類:
public class SpelExpressionParser extends TemplateAwareExpressionParser { @Override protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException { //這里返回了一個(gè)InternalSpelExpressionParser, return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context); } }
InternalSpelExpressionParser類:
class InternalSpelExpressionParser extends TemplateAwareExpressionParser { @Override protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException { try { this.expressionString = expressionString; Tokenizer tokenizer = new Tokenizer(expressionString); tokenizer.process(); this.tokenStream = tokenizer.getTokens(); this.tokenStreamLength = this.tokenStream.size(); this.tokenStreamPointer = 0; this.constructedNodes.clear(); SpelNodeImpl ast = eatExpression(); if (moreTokens()) { throw new SpelParseException(peekToken().startPos, SpelMessage.MORE_INPUT, toString(nextToken())); } Assert.isTrue(this.constructedNodes.isEmpty()); return new SpelExpression(expressionString, ast, this.configuration); } catch (InternalParseException ex) { throw ex.getCause(); } } }
參考文章
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java通過Callable實(shí)現(xiàn)多線程
這篇文章主要介紹了Java通過Callable實(shí)現(xiàn)多線程,Callable的任務(wù)執(zhí)行后可返回值,運(yùn)行Callable任務(wù)可以拿到一個(gè)Future對(duì)象,Future表示異步計(jì)算的結(jié)果,它提供了檢查計(jì)算是否完成的方法,以等待計(jì)算的完成,并檢查計(jì)算的結(jié)果,需要的朋友可以參考下2023-10-10Java lambda表達(dá)式實(shí)現(xiàn)Flink WordCount過程解析
這篇文章主要介紹了Java lambda表達(dá)式實(shí)現(xiàn)Flink WordCount過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02三分鐘帶你掌握J(rèn)ava開發(fā)圖片驗(yàn)證碼功能方法
這篇文章主要來為大家詳細(xì)介紹Java實(shí)現(xiàn)開發(fā)圖片驗(yàn)證碼的具體方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2023-02-02Java數(shù)據(jù)結(jié)構(gòu)之順序表詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之順序表詳解,線性表在邏輯上是線性結(jié)構(gòu),也就說是連續(xù)的一條直線。但是在物理結(jié)構(gòu)上并不一定是連續(xù)的,線性表在物理上存儲(chǔ)時(shí),通常以數(shù)組和鏈?zhǔn)浇Y(jié)構(gòu)的形式存儲(chǔ),需要的朋友可以參考下2023-07-07