解释器模式
1、解释器模式介绍
解释器模式(Interpreter Pattern)是一种行为设计模式,它定义了一个语言的语法表示,并且使用解释器来解释这个语法。
该模式的核心思想是将一个语言表达式表示为一个抽象语法树,然后定义解释器来遍历这棵语法树并执行相应的操作。解释器模式常用于处理特定的语法或规则,并且可以根据需求进行灵活的扩展。
1.1 解释器模式基本实现
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这 样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
解释器模式结构图:
以下是解释器模式的关键角色:
抽象表达式(Abstract Expression):定义了解释器的接口,其中包含了一个抽象的interpret()方法,所有具体表达式都必须实现这个接口。
终结符表达式(Terminal Expression):实现了抽象表达式接口,并表示语法中的终结符(即不可再分的最小单元),例如变量或常量。
非终结符表达式(Non-terminal Expression):实现了抽象表达式接口,并表示语法中的非终结符,即可以由其他表达式组合而成的复杂表达式。
上下文(Context):包含解释器解释的上下文信息,一般是一个包含了各种全局信息的数据结构。
解释器模式的工作流程如下:
客户端创建并配置解释器的上下文。
客户端创建具体的解释器对象,并用抽象表达式接口进行引用。
客户端将表达式构建成抽象语法树。
客户端调用解释器的interpret()方法,传入上下文对象,执行解释操作。AbstractExpression(抽象表达式),声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
/** * @author Shier * CreateTime 2023/5/24 19:27 * 抽象表达式 */ public abstract class AbstractExpression { /** * 解释操作 - 类似翻译 * @param context */ public abstract void interpret(Context context); }
TerminalExpression(终结符表达式),实现与文法中的终结符相关联 的解释操作。实现抽象表达式中所要求的接口,主要是一个interpret()方 法。文法中每一个终结符都有一个具体终结表达式与之相对应。
/** * @author Shier * CreateTime 2023/5/24 19:29 * 终结者表达式 */ public class TerminalExpression extends AbstractExpression{ @Override public void interpret(Context context) { System.out.println("终端解释器"); } }
NonterminalExpression(非终结符表达式),为文法中的非终结符实现 解释操作。对文法中每一条规则R1、R2、…、Rn都需要一个具体的非终结符 表达式类。通过实现抽象表达式的interpret()方法实现解释操作。解释操 作以递归方式调用上面所提到的代表R1、R2、…、Rn中各个符号的实例变量。
/** * @author Shier * CreateTime 2023/5/24 19:29 * 非终结者表达式 */ public class NotTerminalExpression extends AbstractExpression{ @Override public void interpret(Context context) { System.out.println("非终端解释器"); } }
Context,包含解释器之外的一些全局信息。
/** * @author Shier * CreateTime 2023/5/24 19:31 * 解释器外的全局信息 */ public class Context { private String input; private String outPut; public String getInput() { return input; } public void setInput(String input) { this.input = input; } public String getOutPut() { return outPut; } public void setOutPut(String outPut) { this.outPut = outPut; } }
客户端代码,构建表示该文法定义的语言中一个特定的句子的抽象语法树。调用解释操作。
/** * @author Shier * CreateTime 2023/5/24 19:31 */ public class ExpressClient { public static void main(String[] args) { Context context = new Context(); ArrayList<AbstractExpression> arrayList = new ArrayList<>(); arrayList.add(new TerminalExpression()); arrayList.add(new NotTerminalExpression()); arrayList.add(new TerminalExpression()); arrayList.add(new TerminalExpression()); for (AbstractExpression expression : arrayList) { expression.interpret(context); } } }
最终得出的结果:
到此,应该大概知道了解释器模式的使用过程了,下面再来看看具体的例子怎么讲述这个解释器模式吧
2、具体例子实现解释器模式
假设您是一位旅行者,正在国外旅游。在您旅行的过程中,您需要与当地人交流,但您并不懂得当地的语言。您可以雇佣一名翻译来帮助您与当地人沟通。
2.1 不使用解释器模式进行翻译
/** * @author Shier * CreateTime 2023/5/24 19:50 */ public class ExpreMain { public static void main(String[] args) { String sentence = "How much does it cost?"; String language = "中文"; String translatedSentence; if (language.equals("English")) { translatedSentence = translateToEnglish(sentence); } else if (language.equals("Spanish")) { translatedSentence = translateToSpanish(sentence); } else if (language.equals("French")) { translatedSentence = translateToFrench(sentence); } else if (language.equals("中文")) { translatedSentence = translateToChinese(sentence); } else { translatedSentence = "Language not supported"; } System.out.println("Translated sentence: " + translatedSentence); } private static String translateToEnglish(String sentence) { // 实现将句子翻译为英语的逻辑 return sentence; } private static String translateToChinese(String sentence) { // 实现将句子翻译为英语的逻辑 return "要多少钱?"; } private static String translateToSpanish(String sentence) { // 实现将句子翻译为西班牙语的逻辑 return "¿Cuánto cuesta?"; } private static String translateToFrench(String sentence) { // 实现将句子翻译为法语的逻辑 return "Combien ça coûte ?"; } }
翻译得到的结果:
在这个例子中,我们使用简单的条件语句来根据当前语言选择相应的翻译方法。根据当前语言环境,我们调用对应的翻译方法来翻译句子。最后,我们输出翻译后的句子。
虽然这种方法可以实现语言翻译的功能,但它存在一些问题:
增加新的语言需要修改代码:如果需要添加新的语言支持,我们需要修改主程序的条件语句,并添加一个新的翻译方法。这违反了开闭原则,增加了代码的耦合性。
代码可读性差:随着语言支持的增加,条件语句会变得越来越复杂,代码可读性和维护性下降。
缺乏灵活性和扩展性:在解释器模式中,我们可以轻松地添加新的语法规则或扩展现有的规则,而不会对现有代码产生太大的影响。但是,使用条件语句的方法不够灵活,添加新的语言或语法规则需要修改大量的代码。
总结起来,虽然在简单的场景下,使用条件语句可能是一种简单直接的解决方案,但是随着需求的变化和复杂性的增加,这种方法会导致代码的维护困难、扩展性差以及违反设计原则。而解释器模式能够更好地管理和扩展语法规则,提供了更灵活、可扩展的解决方案。
2.2 使用解释器模式
具体的实现过程:
抽象表达类:
/** * @author Shier */ public interface Expression { String interpret(Context context); }
语言表达符号:
/** * @author Shier */ public class LanguageExpression implements Expression { private String language; public LanguageExpression(String language) { this.language = language; } @Override public String interpret(Context context) { return context.translate(language); } }
非语言表达符:
/** * @author Shier */ public class NonLanguageExpression implements Expression { private Expression firstExpression; private Expression secondExpression; public NonLanguageExpression(Expression firstExpression, Expression secondExpression) { this.firstExpression = firstExpression; this.secondExpression = secondExpression; } @Override public String interpret(Context context) { String firstTranslation = firstExpression.interpret(context); String secondTranslation = secondExpression.interpret(context); return firstTranslation + " " + secondTranslation; } }
Context:
import java.util.HashMap; import java.util.Map; public class Context { private Map<String, String> translations; public Context() { translations = new HashMap<>(); } public void addTranslation(String language, String translation) { translations.put(language, translation); } public String translate(String language) { return translations.get(language); } }
客户端:
public class InterpreterExample { public static void main(String[] args) { Context context = new Context(); context.addTranslation("English", "Hello"); context.addTranslation("French", "Bonjour"); context.addTranslation("Spanish", "Hola"); Expression englishExpression = new LanguageExpression("English"); Expression frenchExpression = new LanguageExpression("French"); Expression spanishExpression = new LanguageExpression("Spanish"); Expression combinedExpression = new NonLanguageExpression(frenchExpression,spanishExpression); String travelerLanguage = "French"; String translatedText = null; if (travelerLanguage.equals("English")) { translatedText = englishExpression.interpret(context); } else if (travelerLanguage.equals("French")) { translatedText = combinedExpression.interpret(context); } else if (travelerLanguage.equals("Spanish")) { translatedText = spanishExpression.interpret(context); } System.out.println("Traveler: " + translatedText); } }
结果:
虽然上面的翻译例子,可以使用适配器模式进行,但是和解释器模式实现起来的过程还是不一样的,但都能达到相同的目的。适配器模式和解释器模式的区别:
解释器模式: 解释器模式用于解析和解释特定类型的语言或表达式。它通常用于构建编程语言解释器或规则引擎。该模式的核心思想是将语言或表达式表示为一个抽象语法树,并定义一组解释器来执行相应的操作。
解释器模式的关键组成部分包括以下几个角色:
抽象表达式(Abstract Expression):定义了解释器的接口,包含解释方法interpret()。
终结表达式(Terminal Expression):表示语法中的终结符,执行具体解释操作。
非终结表达式(Non-terminal Expression):表示语法中的非终结符,通常由多个终结符和/或非终结符组合而成。
解释器模式的主要目的是通过定义一组解释器,将语言或表达式转换为可执行的操作,以实现特定的功能。
适配器模式: 适配器模式用于将一个类的接口转换为另一个类的接口,使得原本由于接口不兼容而无法工作的类能够一起工作。适配器模式允许已存在的类与其他类协同工作,而无需修改其源代码。
适配器模式的核心思想是引入一个适配器类,该类实现了目标接口,并在内部包含了要适配的类的实例。适配器类通过调用包含的实例的方法,将对目标接口的调用转发给适配的类。
适配器模式的主要目的是解决不同接口之间的兼容性问题,使得不兼容的类能够协同工作。
总结: 解释器模式和适配器模式的区别在于它们解决的问题领域和应用场景不同。解释器模式主要用于解析和执行特定类型的语言或表达式,而适配器模式主要用于将不兼容的接口进行适配,使得不兼容的类能够协同工作。
2.3 使用解释器模式进行不同的日期格式转换
import java.time.LocalDate; import java.time.format.DateTimeFormatter; // 抽象表达式接口 interface Expression { LocalDate interpret(String context); } // 具体表达式类 - 解析“yyyy-MM-dd”格式的日期 class YearMonthDayExpression implements Expression { @Override public LocalDate interpret(String context) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); return LocalDate.parse(context, formatter); } } // 具体表达式类 - 解析“dd/MM/yyyy”格式的日期 class DayMonthYearExpression implements Expression { @Override public LocalDate interpret(String context) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); return LocalDate.parse(context, formatter); } } // 上下文类 class Context { private Expression expression; public Context(Expression expression) { this.expression = expression; } public LocalDate interpret(String context) { return expression.interpret(context); } } // 示例 public class Main { public static void main(String[] args) { // 输入日期字符串 String dateString = "2023-05-24"; // 创建解析器 Expression expression = new YearMonthDayExpression(); Context context = new Context(expression); // 解析日期 LocalDate date = context.interpret(dateString); // 打印解析结果 System.out.println("Parsed date: " + date); } }
在上面的示例中,我们首先定义了一个抽象表达式接口Expression,其中包含一个interpret方法用于解释日期字符串。然后,我们实现了两个具体的表达式类YearMonthDayExpression和DayMonthYearExpression,分别用于解析不同格式的日期字符串。
我们还创建了一个上下文类Context,它接收一个表达式作为参数,并通过调用表达式的interpret方法来解析日期字符串。
在示例的main方法中,我们定义了一个日期字符串dateString,然后创建了一个YearMonthDayExpression实例作为解析器,并将其传递给上下文对象。最后,我们使用上下文对象解析日期字符串,并打印解析结果。
这只是一个简单的示例,你可以根据实际需求进行扩展和修改。通过使用解释器模式,你可以根据不同的日期格式创建不同的解析器,并使用统一的接口进行日期解析。
到此应该知道解释器模式的用处,我们再来总结一下解释器模式
3、解释器模式总结
3.1 使用场景
构建编程语言解释器:解释器模式非常适合构建编程语言解释器,可以将语言的语法规则定义为解释器中的表达式类,并执行对应的操作。
规则引擎:解释器模式可以用于实现规则引擎,通过解释器来解析和执行规则,对输入数据进行处理和判断。
数学表达式解析:解释器模式可用于解析和计算数学表达式,将表达式表示为抽象语法树,并通过解释器执行相应的操作。
查询语言解析:解释器模式可以用于解析和执行查询语言,例如数据库查询语言等
3.2 解释器模式优点
扩展性好:通过增加新的表达式类,可以灵活地扩展解释器的功能。
易于实现语法:解释器模式可以通过解析语法树来定义语言的语法规则,使得语法定义清晰明确。
易于实现特定领域语言:可以使用解释器模式来实现特定领域语言(Domain-Specific Language, DSL),以满足特定问题领域的需求。
3.3 解释器模式缺点
执行效率相对较低:解释器模式通常需要解析和执行语法树,可能会导致较低的执行效率。
可能引起类膨胀:如果语法规则非常复杂,可能会导致需要大量的表达式类,从而增加了类的数量,使代码复杂度增加。
实现规则引擎,通过解释器来解析和执行规则,对输入数据进行处理和判断。
3. 数学表达式解析:解释器模式可用于解析和计算数学表达式,将表达式表示为抽象语法树,并通过解释器执行相应的操作。
4. 查询语言解析:解释器模式可以用于解析和执行查询语言,例如数据库查询语言等。
3.2 解释器模式优点
扩展性好:通过增加新的表达式类,可以灵活地扩展解释器的功能。
易于实现语法:解释器模式可以通过解析语法树来定义语言的语法规则,使得语法定义清晰明确。
易于实现特定领域语言:可以使用解释器模式来实现特定领域语言(Domain-Specific Language, DSL),以满足特定问题领域的需求。
3.3 解释器模式缺点
- 执行效率相对较低:解释器模式通常需要解析和执行语法树,可能会导致较低的执行效率。
- 可能引起类膨胀:如果语法规则非常复杂,可能会导致需要大量的表达式类,从而增加了类的数量,使代码复杂度增加。