點(diǎn)贊再看,養(yǎng)成習(xí)慣是尔,公眾號(hào)搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章捺萌。
前言
- 23種設(shè)計(jì)模式速記
- 單例(singleton)模式
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
- 建造者/構(gòu)建器(builder)模式
- 原型(prototype)模式
- 享元(flyweight)模式
- 外觀(facade)模式
- 適配器(adapter)模式
- 裝飾(decorator)模式
- 觀察者(observer)模式
- 策略(strategy)模式
- 橋接(bridge)模式
- 模版方法(template method)模式
- 責(zé)任鏈(chain of responsibility)模式
- 組合(composite)模式
- 代理(proxy)模式
- 備忘錄(memento)模式
- 命令(command)模式
- 狀態(tài)(state)模式
- 中介者(mediator)模式
- 迭代器(iterator)模式
- 持續(xù)更新中......
23種設(shè)計(jì)模式快速記憶的請(qǐng)看上面第一篇,本篇和大家一起來學(xué)習(xí)解釋器模式相關(guān)內(nèi)容膘茎。
模式定義
給分析對(duì)象定義一個(gè)語言桃纯,并定義該語言的文法表示,再設(shè)計(jì)一個(gè)解析器來解釋語言中的句子披坏。也就是說态坦,用編譯語言的方式來分析應(yīng)用中的實(shí)例。這種模式實(shí)現(xiàn)了文法表達(dá)式處理的接口棒拂,該接口解釋一個(gè)特定的上下文伞梯。
這里提到的文法和句子的概念同編譯原理中的描述相同,“文法”指語言的語法規(guī)則,而“句子”是語言集中的元素壮锻。例如琐旁,漢語中的句子有很多,“我是中國(guó)人”是其中的一個(gè)句子猜绣,可以用一棵語法樹來直觀地描述語言中的句子灰殴。
模式的結(jié)構(gòu)和實(shí)現(xiàn)
解釋器模式常用于對(duì)簡(jiǎn)單語言的編譯或分析實(shí)例中,為了掌握好它的結(jié)構(gòu)與實(shí)現(xiàn)掰邢,必須先了解編譯原理中的“文法牺陶、句子、語法樹”等相關(guān)概念辣之。
文法
文法是用于描述語言的語法結(jié)構(gòu)的形式規(guī)則掰伸。沒有規(guī)矩不成方圓,例如怀估,有些人認(rèn)為完美愛情的準(zhǔn)則是“相互吸引狮鸭、感情專一、任何一方都沒有戀愛經(jīng)歷”多搀,雖然最后一條準(zhǔn)則較苛刻歧蕉,但任何事情都要有規(guī)則,語言也一樣康铭,不管它是機(jī)器語言還是自然語言惯退,都有它自己的文法規(guī)則。例如从藤,中文中的“句子”的文法如下催跪。
〈句子〉::=〈主語〉〈謂語〉〈賓語〉
〈主語〉::=〈代詞〉|〈名詞〉
〈謂語〉::=〈動(dòng)詞〉
〈賓語〉::=〈代詞〉|〈名詞〉
〈代詞〉你|我|他
〈名詞〉7大學(xué)生I筱霞I英語
〈動(dòng)詞〉::=是|學(xué)習(xí)
注:這里的符號(hào)“::=”表示“定義為”的意思,用“〈”和“〉”括住的是非終結(jié)符夷野,沒有括住的是終結(jié)符懊蒸。
句子
句子是語言的基本單位,是語言集中的一個(gè)元素扫责,它由終結(jié)符構(gòu)成榛鼎,能由“文法”推導(dǎo)出。例如鳖孤,上述文法可以推出“我是大學(xué)生”者娱,所以它是句子。
語法樹
語法樹是句子結(jié)構(gòu)的一種樹型表示苏揣,它代表了句子的推導(dǎo)結(jié)果黄鳍,它有利于理解句子語法結(jié)構(gòu)的層次。下圖所示是“我是大學(xué)生”的語法樹平匈。
解釋器模式的結(jié)構(gòu)與組合模式相似框沟,不過其包含的組成元素比組合模式多藏古,而且組合模式是對(duì)象結(jié)構(gòu)型模式,而解釋器模式是類行為型模式忍燥。
模式的實(shí)現(xiàn)
解釋器模式實(shí)現(xiàn)的關(guān)鍵是定義文法規(guī)則拧晕、設(shè)計(jì)終結(jié)符類與非終結(jié)符類、畫出結(jié)構(gòu)圖梅垄,必要時(shí)構(gòu)建語法樹厂捞,其代碼結(jié)構(gòu)如下:
package com.niuh.designpattern.interpreter.v1;
/**
* <p>
* 解釋器模式
* </p>
*/
public class InterpreterPattern {
}
//抽象表達(dá)式類
interface AbstractExpression {
public Object interpret(String info); //解釋方法
}
//終結(jié)符表達(dá)式類
class TerminalExpression implements AbstractExpression {
public Object interpret(String info) {
//對(duì)終結(jié)符表達(dá)式的處理
return null;
}
}
//非終結(jié)符表達(dá)式類
class NonterminalExpression implements AbstractExpression {
private AbstractExpression exp1;
private AbstractExpression exp2;
public Object interpret(String info) {
//非對(duì)終結(jié)符表達(dá)式的處理
return null;
}
}
//環(huán)境類
class Context {
private AbstractExpression exp;
public Context() {
//數(shù)據(jù)初始化
}
public void operation(String info) {
//調(diào)用相關(guān)表達(dá)式類的解釋方法
}
}
解決的問題
對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器。
模式組成
組成(角色) | 作用 |
---|---|
抽象表達(dá)式(Abstract Expression)角色 | 定義解釋器的接口队丝,約定解釋器的解釋操作靡馁,主要包含解釋方法 interpret()。 |
終結(jié)符表達(dá)式(Terminal Expression)角色 | 是抽象表達(dá)式的子類机久,用來實(shí)現(xiàn)文法中與終結(jié)符相關(guān)的操作臭墨,文法中的每一個(gè)終結(jié)符都有一個(gè)具體終結(jié)表達(dá)式與之相對(duì)應(yīng)。 |
非終結(jié)符表達(dá)式(Nonterminal Expression)角色 | 也是抽象表達(dá)式的子類膘盖,用來實(shí)現(xiàn)文法中與非終結(jié)符相關(guān)的操作胧弛,文法中的每條規(guī)則都對(duì)應(yīng)于一個(gè)非終結(jié)符表達(dá)式。 |
環(huán)境(Context)角色 | 通常包含各個(gè)解釋器需要的數(shù)據(jù)或是公共的功能侠畔,一般用來傳遞被所有解釋器共享的數(shù)據(jù)叶圃,后面的解釋器可以從這里獲取這些值。 |
客戶端(Client) | 主要任務(wù)是將需要分析的句子或表達(dá)式轉(zhuǎn)換成使用解釋器對(duì)象描述的抽象語法樹践图,然后調(diào)用解釋器的解釋方法,當(dāng)然也可以通過環(huán)境角色間接訪問解釋器的解釋方法沉馆。 |
實(shí)例說明
實(shí)例概況
用解釋器模式設(shè)計(jì)一個(gè)北京公交車卡的讀卡器程序码党。
說明:假如北京公交車讀卡器可以判斷乘客的身份,如果是“海淀區(qū)”或者“朝陽區(qū)”的“老人” “婦女”“兒童”就可以免費(fèi)乘車斥黑,其他人員乘車一次扣 2 元揖盘。
分析:本實(shí)例用“解釋器模式”設(shè)計(jì)比較適合,首先設(shè)計(jì)其文法規(guī)則如下锌奴。
<expression> ::= <city>的<person>
<city> ::= 海淀區(qū)|朝陽區(qū)
<person> ::= 老人|婦女|兒童
然后兽狭,根據(jù)文法規(guī)則按以下步驟設(shè)計(jì)公交車卡的讀卡器程序的類圖。
使用步驟
步驟1:定義一個(gè)抽象表達(dá)式(Expression)接口鹿蜀,它包含了解釋方法 interpret(String info)箕慧。
//抽象表達(dá)式類
interface Expression {
public boolean interpret(String info);
}
步驟2:定義一個(gè)終結(jié)符表達(dá)式(Terminal Expression)類,它用集合(Set)類來保存滿足條件的城市或人茴恰,并實(shí)現(xiàn)抽象表達(dá)式接口中的解釋方法 interpret(Stringinfo)颠焦,用來判斷被分析的字符串是否是集合中的終結(jié)符。
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:定義一個(gè)非終結(jié)符表達(dá)式(AndExpressicm)類往枣,它也是抽象表達(dá)式的子類伐庭,它包含滿足條件的城市的終結(jié)符表達(dá)式對(duì)象和滿足條件的人員的終結(jié)符表達(dá)式對(duì)象粉渠,并實(shí)現(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:定義一個(gè)環(huán)境(Context)類霸株,它包含解釋器需要的數(shù)據(jù),完成對(duì)終結(jié)符表達(dá)式的初始化集乔,并定義一個(gè)方法 freeRide(String info) 調(diào)用表達(dá)式對(duì)象的解釋方法來對(duì)被分析的字符串進(jìn)行解釋去件。
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 + ",您本次乘車免費(fèi)饺著!");
} else {
System.out.println(info + "箫攀,您不是免費(fèi)人員,本次乘車扣費(fèi)2元幼衰!");
}
}
}
步驟5:客戶端測(cè)試
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("南京的年輕人");
}
}
輸出結(jié)果
您是海淀區(qū)的老人靴跛,您本次乘車免費(fèi)!
海淀區(qū)的年輕人渡嚣,您不是免費(fèi)人員梢睛,本次乘車扣費(fèi)2元!
您是朝陽區(qū)的婦女识椰,您本次乘車免費(fèi)绝葡!
您是朝陽區(qū)的兒童,您本次乘車免費(fèi)腹鹉!
南京的年輕人藏畅,您不是免費(fèi)人員,本次乘車扣費(fèi)2元功咒!
優(yōu)點(diǎn)
解釋器模式是一種類行為型模式愉阎,其主要優(yōu)點(diǎn)如下。
- 擴(kuò)展性好力奋。由于在解釋器模式中使用類來表示語言的文法規(guī)則榜旦,因此可以通過繼承等機(jī)制來改變或擴(kuò)展文法。
- 容易實(shí)現(xiàn)景殷。在語法樹中的每個(gè)表達(dá)式節(jié)點(diǎn)類都是相似的溅呢,所以實(shí)現(xiàn)其文法較為容易。
缺點(diǎn)
- 執(zhí)行效率較低猿挚。解釋器模式中通常使用大量的循環(huán)和遞歸調(diào)用咐旧,當(dāng)要解釋的句子較復(fù)雜時(shí),其運(yùn)行速度很慢亭饵,且代碼的調(diào)試過程也比較麻煩休偶。
- 會(huì)引起類膨脹。解釋器模式中的每條規(guī)則至少需要定義一個(gè)類辜羊,當(dāng)包含的文法規(guī)則很多時(shí)踏兜,類的個(gè)數(shù)將急劇增加词顾,導(dǎo)致系統(tǒng)難以管理與維護(hù)。
- 可應(yīng)用的場(chǎng)景比較少碱妆。在軟件開發(fā)中肉盹,需要定義語言文法的應(yīng)用實(shí)例非常少,所以這種模式很少被使用到疹尾。
應(yīng)用場(chǎng)景
- 當(dāng)語言的文法較為簡(jiǎn)單上忍,且執(zhí)行效率不是關(guān)鍵問題時(shí)。
- 當(dāng)問題重復(fù)出現(xiàn)纳本,且可以用一種簡(jiǎn)單的語言來進(jìn)行表達(dá)時(shí)窍蓝。
- 當(dāng)一個(gè)語言需要解釋執(zhí)行,并且語言中的句子可以表示為一個(gè)抽象語法樹的時(shí)候繁成,如 XML 文檔解釋吓笙。
模式的擴(kuò)展
在項(xiàng)目開發(fā)中,如果要對(duì)數(shù)據(jù)表達(dá)式進(jìn)行分析與計(jì)算巾腕,無須再用解釋器模式進(jìn)行設(shè)計(jì)了面睛,Java 提供了以下強(qiáng)大的數(shù)學(xué)公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等尊搬,它們可以解釋一些復(fù)雜的文法叁鉴,功能強(qiáng)大,使用簡(jiǎn)單佛寿。
現(xiàn)在以 Jep 為例來介紹該工具包的使用方法幌墓。Jep 是 Java expression parser 的簡(jiǎn)稱,即 Java 表達(dá)式分析器冀泻,它是一個(gè)用來轉(zhuǎn)換和計(jì)算數(shù)學(xué)表達(dá)式的 Java 庫克锣。通過這個(gè)程序庫,用戶可以以字符串的形式輸入一個(gè)任意的公式腔长,然后快速地計(jì)算出其結(jié)果。而且 Jep 支持用戶自定義變量验残、常量和函數(shù)捞附,它包括許多常用的數(shù)學(xué)函數(shù)和常量。
使用前先配置依賴包:
<!-- https://mvnrepository.com/artifact/jep/jep -->
<dependency>
<groupId>jep</groupId>
<artifactId>jep</artifactId>
<version>2.24</version>
</dependency>
下面來看一個(gè)案例:
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(); //一個(gè)數(shù)學(xué)表達(dá)式
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("計(jì)算結(jié)果: " + result);
} catch (Throwable e) {
System.out.println("An error occured: " + e.getMessage());
}
}
}
程序運(yùn)行結(jié)果如下:
計(jì)算結(jié)果: 2.0
源碼中的應(yīng)用
SpelExpressionParser中解釋器模式應(yīng)用分析
類圖分析
在下面的類圖中您没,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();
}
}
}
PS:以上代碼提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持續(xù)更新种冬,可以公眾號(hào)搜一搜「 一角錢技術(shù) 」第一時(shí)間閱讀, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄舔糖,歡迎 Star娱两。