設計模式系列—解釋器模式
本篇和大家一起來學習解釋器模式相關內(nèi)容。
模式定義
給分析對象定義一個語言,并定義該語言的文法表示,再設計一個解析器來解釋語言中的句子。也就是說,用編譯語言的方式來分析應用中的實例。這種模式實現(xiàn)了文法表達式處理的接口,該接口解釋一個特定的上下文。
這里提到的文法和句子的概念同編譯原理中的描述相同,“文法”指語言的語法規(guī)則,而“句子”是語言集中的元素。例如,漢語中的句子有很多,“我是中國人”是其中的一個句子,可以用一棵語法樹來直觀地描述語言中的句子。
模式的結構和實現(xiàn)
解釋器模式常用于對簡單語言的編譯或分析實例中,為了掌握好它的結構與實現(xiàn),必須先了解編譯原理中的“文法、句子、語法樹”等相關概念。
文法
文法是用于描述語言的語法結構的形式規(guī)則。沒有規(guī)矩不成方圓,例如,有些人認為完美愛情的準則是“相互吸引、感情專一、任何一方都沒有戀愛經(jīng)歷”,雖然最后一條準則較苛刻,但任何事情都要有規(guī)則,語言也一樣,不管它是機器語言還是自然語言,都有它自己的文法規(guī)則。例如,中文中的“句子”的文法如下。
- 〈句子〉::=〈主語〉〈謂語〉〈賓語〉
- 〈主語〉::=〈代詞〉|〈名詞〉
- 〈謂語〉::=〈動詞〉
- 〈賓語〉::=〈代詞〉|〈名詞〉
- 〈代詞〉你|我|他
- 〈名詞〉7大學生I筱霞I英語
- 〈動詞〉::=是|學習
注:這里的符號“::=”表示“定義為”的意思,用“〈”和“〉”括住的是非終結符,沒有括住的是終結符。
句子
句子是語言的基本單位,是語言集中的一個元素,它由終結符構成,能由“文法”推導出。例如,上述文法可以推出“我是大學生”,所以它是句子。
語法樹
語法樹是句子結構的一種樹型表示,它代表了句子的推導結果,它有利于理解句子語法結構的層次。下圖所示是“我是大學生”的語法樹。
解釋器模式的結構與組合模式相似,不過其包含的組成元素比組合模式多,而且組合模式是對象結構型模式,而解釋器模式是類行為型模式。
模式的實現(xiàn)
解釋器模式實現(xiàn)的關鍵是定義文法規(guī)則、設計終結符類與非終結符類、畫出結構圖,必要時構建語法樹,其代碼結構如下:
- package com.niuh.designpattern.interpreter.v1;
- /**
- * <p>
- * 解釋器模式
- * </p>
- */
- public class InterpreterPattern {
- }
- //抽象表達式類
- interface AbstractExpression {
- public Object interpret(String info); //解釋方法
- }
- //終結符表達式類
- class TerminalExpression implements AbstractExpression {
- public Object interpret(String info) {
- //對終結符表達式的處理
- return null;
- }
- }
- //非終結符表達式類
- class NonterminalExpression implements AbstractExpression {
- private AbstractExpression exp1;
- private AbstractExpression exp2;
- public Object interpret(String info) {
- //非對終結符表達式的處理
- return null;
- }
- }
- //環(huán)境類
- class Context {
- private AbstractExpression exp;
- public Context() {
- //數(shù)據(jù)初始化
- }
- public void operation(String info) {
- //調(diào)用相關表達式類的解釋方法
- }
- }
解決的問題
對于一些固定文法構建一個解釋句子的解釋器。
模式組成
實例說明
實例概況
用解釋器模式設計一個北京公交車卡的讀卡器程序。
說明:假如北京公交車讀卡器可以判斷乘客的身份,如果是“海淀區(qū)”或者“朝陽區(qū)”的“老人” “婦女”“兒童”就可以免費乘車,其他人員乘車一次扣 2 元。
分析:本實例用“解釋器模式”設計比較適合,首先設計其文法規(guī)則如下。
- <expression> ::= <city>的<person>
- <city> ::= 海淀區(qū)|朝陽區(qū)
- <person> ::= 老人|婦女|兒童
然后,根據(jù)文法規(guī)則按以下步驟設計公交車卡的讀卡器程序的類圖。
使用步驟
步驟1:定義一個抽象表達式(Expression)接口,它包含了解釋方法 interpret(String info)。
- //抽象表達式類
- interface Expression {
- public boolean interpret(String info);
- }
步驟2:定義一個終結符表達式(Terminal Expression)類,它用集合(Set)類來保存滿足條件的城市或人,并實現(xiàn)抽象表達式接口中的解釋方法 interpret(Stringinfo),用來判斷被分析的字符串是否是集合中的終結符。
- class TerminalExpression implements Expression {
- private Set<String> set = new HashSet<String>();
- public TerminalExpression(String[] data) {
- for (int i = 0; i < data.length; i++) {
- set.add(data[i]);
- }
- }
- public boolean interpret(String info) {
- if (set.contains(info)) {
- return true;
- }
- return false;
- }
- }
步驟3:定義一個非終結符表達式(AndExpressicm)類,它也是抽象表達式的子類,它包含滿足條件的城市的終結符表達式對象和滿足條件的人員的終結符表達式對象,并實現(xiàn) interpret(String info) 方法,用來判斷被分析的字符串是否是滿足條件的城市中的滿足條件的人員。
- class AndExpression implements Expression {
- private Expression city = null;
- private Expression person = null;
- public AndExpression(Expression city, Expression person) {
- this.city = city;
- this.person = person;
- }
- public boolean interpret(String info) {
- String s[] = info.split("的");
- return city.interpret(s[0]) && person.interpret(s[1]);
- }
- }
步驟4:定義一個環(huán)境(Context)類,它包含解釋器需要的數(shù)據(jù),完成對終結符表達式的初始化,并定義一個方法 freeRide(String info) 調(diào)用表達式對象的解釋方法來對被分析的字符串進行解釋。
- class Context {
- private String[] citys = {"海淀區(qū)", "朝陽區(qū)"};
- private String[] persons = {"老人", "婦女", "兒童"};
- private Expression cityPerson;
- public Context() {
- Expression city = new TerminalExpression(citys);
- Expression person = new TerminalExpression(persons);
- cityPerson = new AndExpression(city, person);
- }
- public void freeRide(String info) {
- boolean ok = cityPerson.interpret(info);
- if (ok) {
- System.out.println("您是" + info + ",您本次乘車免費!");
- } else {
- System.out.println(info + ",您不是免費人員,本次乘車扣費2元!");
- }
- }
- }
步驟5:客戶端測試
- public class InterpreterPattern {
- public static void main(String[] args) {
- Context bus = new Context();
- bus.freeRide("海淀區(qū)的老人");
- bus.freeRide("海淀區(qū)的年輕人");
- bus.freeRide("朝陽區(qū)的婦女");
- bus.freeRide("朝陽區(qū)的兒童");
- bus.freeRide("南京的年輕人");
- }
- }
輸出結果
- 您是海淀區(qū)的老人,您本次乘車免費!
- 海淀區(qū)的年輕人,您不是免費人員,本次乘車扣費2元!
- 您是朝陽區(qū)的婦女,您本次乘車免費!
- 您是朝陽區(qū)的兒童,您本次乘車免費!
- 南京的年輕人,您不是免費人員,本次乘車扣費2元!
優(yōu)點
解釋器模式是一種類行為型模式,其主要優(yōu)點如下。
- 擴展性好。由于在解釋器模式中使用類來表示語言的文法規(guī)則,因此可以通過繼承等機制來改變或擴展文法。
- 容易實現(xiàn)。在語法樹中的每個表達式節(jié)點類都是相似的,所以實現(xiàn)其文法較為容易。
缺點
- 執(zhí)行效率較低。解釋器模式中通常使用大量的循環(huán)和遞歸調(diào)用,當要解釋的句子較復雜時,其運行速度很慢,且代碼的調(diào)試過程也比較麻煩。
- 會引起類膨脹。解釋器模式中的每條規(guī)則至少需要定義一個類,當包含的文法規(guī)則很多時,類的個數(shù)將急劇增加,導致系統(tǒng)難以管理與維護。
- 可應用的場景比較少。在軟件開發(fā)中,需要定義語言文法的應用實例非常少,所以這種模式很少被使用到。
應用場景
- 當語言的文法較為簡單,且執(zhí)行效率不是關鍵問題時。
- 當問題重復出現(xiàn),且可以用一種簡單的語言來進行表達時。
- 當一個語言需要解釋執(zhí)行,并且語言中的句子可以表示為一個抽象語法樹的時候,如 XML 文檔解釋。
模式的擴展
在項目開發(fā)中,如果要對數(shù)據(jù)表達式進行分析與計算,無須再用解釋器模式進行設計了,Java 提供了以下強大的數(shù)學公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它們可以解釋一些復雜的文法,功能強大,使用簡單。
現(xiàn)在以 Jep 為例來介紹該工具包的使用方法。Jep 是 Java expression parser 的簡稱,即 Java 表達式分析器,它是一個用來轉換和計算數(shù)學表達式的 Java 庫。通過這個程序庫,用戶可以以字符串的形式輸入一個任意的公式,然后快速地計算出其結果。而且 Jep 支持用戶自定義變量、常量和函數(shù),它包括許多常用的數(shù)學函數(shù)和常量。
使用前先配置依賴包:
- <!-- https://mvnrepository.com/artifact/jep/jep -->
- <dependency>
- <groupId>jep</groupId>
- <artifactId>jep</artifactId>
- <version>2.24</version>
- </dependency>
下面來看一個案例:
- package com.niuh.designpattern.interpreter.v3;
- import org.nfunk.jep.JEP;
- /**
- * <p>
- * JepDemo
- * </p>
- */
- public class JepDemo {
- public static void main(String[] args) {
- JEP jep = new JEP(); //一個數(shù)學表達式
- String exp = "((a+b)*(c+b))/(c+a)/b"; //給變量賦值
- jep.addVariable("a", 10);
- jep.addVariable("b", 10);
- jep.addVariable("c", 10);
- try { //執(zhí)行
- jep.parseExpression(exp);
- Object result = jep.getValueAsObject();
- System.out.println("計算結果: " + result);
- } catch (Throwable e) {
- System.out.println("An error occured: " + e.getMessage());
- }
- }
- }
程序運行結果如下:
- 計算結果: 2.0
源碼中的應用
SpelExpressionParser中解釋器模式應用分析
類圖分析
在下面的類圖中,Expression是一個接口,相當于我們解釋器模式中的非終結符表達式,而ExpressionParser相當于終結符表達式。根據(jù)不同的Parser對象,返回不同的Expression對象。
部分源碼分析
Expression接口
- //抽象的非終結符表達式
- public interface Expression {
- Object getValue() throws EvaluationException;
- Object getValue(Object rootObject) throws EvaluationException;
- }
SpelExpression類
- //具體的非終結符表達式
- 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
- //具體的非終結符表達式
- public class CompositeStringExpression implements Expression {
- @Override
- public String getValue() throws EvaluationException {
- StringBuilder sb&nbsp;= 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 {
- //解析表達式
- 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對象
- @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);
- }
- }
- //抽象的,由子類去實現(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 {
- //這里返回了一個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();
- }
- }
- }
PS:以上代碼提交在 Github :
https://github.com/Niuh-Study/niuh-designpatterns.git