学习设计模式第十八 - 解释器模式
部分示例代码来自DoFactory。
概述
如果一个特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
意图
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
UML
图1 解释器模式的UML图
参与者
这个模式涉及的类或对象:
-
AbstractExpression
-
定义执行一个操作的接口
-
TerminalExpression
-
实现解释器操作相关的语法中的末端符号。
-
语句中每一个末端符号需要一个实例。
-
NonterminalExpression
-
语法中每一个规则R::=R1R2...Rn需要这样一个类
-
维护R1到Rn每一个符号的AbstractExpression类型的实例变量。
-
实现一个处理语法中非终端节点的解释操作。解释操作在表示R1到Rn的变量上递归调用自身。
-
Context
-
保存对于解释器的全局信息
-
Client
-
构建一个抽象语法树表示语法定义的语言中一个特定的句子。抽象语法树由NonterminalExpression和TerminalExpressin类型的实例组装而成。
-
调用解释操作
适用性
如果你的应用很复杂且需要高级配置系统,你可以提供一个脚本语言使终端用户可以通过一些简单的脚本来操作应用程序。解释器模式解决此类通过创建一种脚本语言使终端用户可以自定义解决方案的特定问题。
事实是如果你真需要这种类型的控制,使用已有的命令解释器或表达式计算工具可能更简单更快速。VBScript可以考虑,另外还有Microsoft新支持的.NET动态语言(IronPython等)。
这种类型的问题使它们自己拥有了一种语言的特性。这种语言描述了一个应该被设计为易于理解、易于定义的问题域。另外,这个语言需要映射到一个语法。语法通常是层次树状结构,这种结构自上而下穿过多个层次以终端结点(也称字面量)结束。这种类型的问题,表示为一个语法,可以使用解释器模式来实现。知名的汉诺塔问题是这个类型问题的一个例子,其可以通过一个简单的语言来编码并使用解释器模式来实现。参见:http://home.earthlink.net/~huston2/ps/hanoi_article.html。
解释器模式与其它几种模式有一些相似处。正如状态模式和策略模式,它将处理委托给一系列专用的类型。其与组合模式也有一些相似处;基本的解释器是一个Composite的增强,虽然比起Composite模式,它通常需要更复杂的对象组合在一起。
当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
-
该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
-
效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的。
总结,当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
DoFactory GoF代码
这个解释器模式的实例中,定义了一种语法并提供一个解释器来处理待分析的语句。
// Interpreter pattern // Structural example using System; using System.Collections; namespace DoFactory.GangOfFour.Interpreter.Structural { // MainApp test application class MainApp { static void Main() { Context context = new Context(); // Usually a tree ArrayList list = new ArrayList(); // Populate 'abstract syntax tree' list.Add(new TerminalExpression()); list.Add(new NonterminalExpression()); list.Add(new TerminalExpression()); list.Add(new TerminalExpression()); // Interpret foreach (AbstractExpression exp in list) { exp.Interpret(context); } // Wait for user Console.ReadKey(); } } // "Context" class Context { } // "AbstractExpression" abstract class abstract class AbstractExpression { public abstract void Interpret(Context context); } // "TerminalExpression" class TerminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("Called Terminal.Interpret()"); } } // "NonterminalExpression" class NonterminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("Called Nonterminal.Interpret()"); } } }
这个解释器模式应用的例子演示了将一个罗马数字转换为一个十进制数。
例子中涉及到的类与解释器模式中标准的类对应关系如下:
-
AbstractExpression – Expression
-
TerminalExpression – ThousandExpression, HundredExpression, TenExpression, OneExpression
-
NonterminalExpression – not used
-
Context – Context
-
Client – InterpreterApp
// Interpreter pattern // Real World example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Interpreter.RealWorld { // MainApp test application class MainApp { static void Main() { string roman = "MCMXXVIII"; Context context = new Context(roman); // Build the 'parse tree' List<Expression> tree = new List<Expression>(); tree.Add(new ThousandExpression()); tree.Add(new HundredExpression()); tree.Add(new TenExpression()); tree.Add(new OneExpression()); // Interpret foreach (Expression exp in tree) { exp.Interpret(context); } Console.WriteLine("{0} = {1}", roman, context.Output); // Wait for user Console.ReadKey(); } } // "Context" class Context { private string _input; private int _output; // Constructor public Context(string input) { this._input = input; } // Gets or sets input public string Input { get { return _input; } set { _input = value; } } // Gets or sets output public int Output { get { return _output; } set { _output = value; } } } // "AbstractExpression" abstract class Expression { public void Interpret(Context context) { if (context.Input.Length == 0) return; if (context.Input.StartsWith(Nine())) { context.Output += (9 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Four())) { context.Output += (4 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Five())) { context.Output += (5 * Multiplier()); context.Input = context.Input.Substring(1); } while (context.Input.StartsWith(One())) { context.Output += (1 * Multiplier()); context.Input = context.Input.Substring(1); } } public abstract string One(); public abstract string Four(); public abstract string Five(); public abstract string Nine(); public abstract int Multiplier(); } // "TerminalExpression" // Thousand checks for the Roman Numeral M class ThousandExpression : Expression { public override string One() { return "M"; } public override string Four() { return " "; } public override string Five() { return " "; } public override string Nine() { return " "; } public override int Multiplier() { return 1000; } } // "TerminalExpression" // Hundred checks C, CD, D or CM class HundredExpression : Expression { public override string One() { return "C"; } public override string Four() { return "CD"; } public override string Five() { return "D"; } public override string Nine() { return "CM"; } public override int Multiplier() { return 100; } } // "TerminalExpression" // Ten checks for X, XL, L and XC class TenExpression : Expression { public override string One() { return "X"; } public override string Four() { return "XL"; } public override string Five() { return "L"; } public override string Nine() { return "XC"; } public override int Multiplier() { return 10; } } // "TerminalExpression" // One checks for I, II, III, IV, V, VI, VI, VII, VIII, IX class OneExpression : Expression { public override string One() { return "I"; } public override string Four() { return "IV"; } public override string Five() { return "V"; } public override string Nine() { return "IX"; } public override int Multiplier() { return 1; } } }
这段.NET优化版代码实现了与上面例子相同的功能,其中采用了更多现代化的.NET内置特性。上面代码中的抽象类由于没有实现在下面代码中被接口代替。另外,存储表达式(ThousandExpression, HundredExpression等)集合的分析树实现为一个类型参数为Expression的泛型列表。例子中也使用.NET3.0中对象初始化器和自动属性等特性。
// Interpreter pattern // .NET Optimized example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Interpreter.NETOptimized { class MainApp { static void Main() { // Construct the 'parse tree' var tree = new List<Expression> { new ThousandExpression(), new HundredExpression(), new TenExpression(), new OneExpression() }; // Create the context (i.e. roman value) string roman = "MCMXXVIII"; var context = new Context { Input = roman }; // Interpret tree.ForEach(e => e.Interpret(context)); Console.WriteLine("{0} = {1}", roman, context.Output); // Wait for user Console.ReadKey(); } } // "Context" class Context { public string Input { get; set; } public int Output { get; set; } } // "AbstractExpression" abstract class Expression { public void Interpret(Context context) { if (context.Input.Length == 0) return; if (context.Input.StartsWith(Nine())) { context.Output += (9 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Four())) { context.Output += (4 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Five())) { context.Output += (5 * Multiplier()); context.Input = context.Input.Substring(1); } while (context.Input.StartsWith(One())) { context.Output += (1 * Multiplier()); context.Input = context.Input.Substring(1); } } public abstract string One(); public abstract string Four(); public abstract string Five(); public abstract string Nine(); public abstract int Multiplier(); } // Thousand checks for the Roman Numeral M // "TerminalExpression" class ThousandExpression : Expression { public override string One() { return "M"; } public override string Four() { return " "; } public override string Five() { return " "; } public override string Nine() { return " "; } public override int Multiplier() { return 1000; } } // Hundred checks C, CD, D or CM // "TerminalExpression" class HundredExpression : Expression { public override string One() { return "C"; } public override string Four() { return "CD"; } public override string Five() { return "D"; } public override string Nine() { return "CM"; } public override int Multiplier() { return 100; } } // Ten checks for X, XL, L and XC // "TerminalExpression" class TenExpression : Expression { public override string One() { return "X"; } public override string Four() { return "XL"; } public override string Five() { return "L"; } public override string Nine() { return "XC"; } public override int Multiplier() { return 10; } } // One checks for I, II, III, IV, V, VI, VI, VII, VIII, IX // "TerminalExpression" class OneExpression : Expression { public override string One() { return "I"; } public override string Four() { return "IV"; } public override string Five() { return "V"; } public override string Nine() { return "IX"; } public override int Multiplier() { return 1; } } }
来自《大话设计模式》的例子
这个例子使用解释器模式实现了一个乐谱解释程序,这个例子比较简单,只有终结符表达式,没有非终结符表达式。
UML:
图2.音乐解释器例子的UML
using System; using System.Collections.Generic; using System.Text; namespace 解释器模式 { class Program { static void Main(string[] args) { PlayContext context = new PlayContext(); //音乐-上海滩 Console.WriteLine("上海滩:"); //context.演奏文本 = "T 500 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 D 0.5 E 0.5 G 3 D 0.5 E 0.5 O 1 A 3 A 0.5 O 2 C 0.5 D 1.5 E 0.5 D 0.5 O 1 B 0.5 A 0.5 O 2 C 0.5 O 1 G 3 P 0.5 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 D 0.5 E 0.5 G 3 D 0.5 E 0.5 O 1 A 3 A 0.5 O 2 C 0.5 D 1.5 E 0.5 D 0.5 O 1 B 0.5 A 0.5 G 0.5 O 2 C 3 P 0.5 O 3 C 0.5 C 0.5 O 2 A 0.5 O 3 C 2 P 0.5 O 2 A 0.5 O 3 C 0.5 O 2 A 0.5 G 2.5 G 0.5 E 0.5 A 1.5 G 0.5 C 1 D 0.25 C 0.25 D 0.5 E 2.5 E 0.5 E 0.5 D 0.5 E 2.5 O 3 C 0.5 C 0.5 O 2 B 0.5 A 3 E 0.5 E 0.5 D 1.5 E 0.5 O 3 C 0.5 O 2 B 0.5 A 0.5 E 0.5 G 2 P 0.5 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 D 0.5 E 0.5 G 3 D 0.5 E 0.5 O 1 A 3 A 0.5 O 2 C 0.5 D 1.5 E 0.5 D 0.5 O 1 B 0.5 A 0.5 G 0.5 O 2 C 3 "; context.PlayText = "T 500 O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3 "; //音乐-隐形的翅膀 //Console.WriteLine("隐形的翅膀:"); //context.演奏文本 = "T 1000 O 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 E 1 D 0.5 C 0.5 C 0.5 C 0.5 C 0.5 O 1 A 0.25 G 0.25 G 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 G 0.5 D 0.25 E 0.25 D 0.5 C 0.25 D 0.25 D 1 A 0.5 G 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 E 0.5 D 0.5 C 0.5 C 0.25 D 0.25 O 1 A 1 G 0.5 A 0.5 O 2 C 1.5 D 0.25 E 0.25 D 1 E 0.5 C 0.5 C 3 O 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 E 1 D 0.5 C 0.5 C 0.5 C 0.5 C 0.5 O 1 A 0.25 G 0.25 G 1 G 0.5 O 2 C 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 G 0.5 D 0.25 E 0.25 D 0.5 C 0.25 D 0.25 D 1 A 0.5 G 0.5 E 1.5 G 0.5 G 0.5 G 0.5 A 0.5 G 0.5 E 0.5 D 0.5 C 0.5 C 0.25 D 0.25 O 1 A 1 G 0.5 A 0.5 O 2 C 1.5 D 0.25 E 0.25 D 1 E 0.5 C 0.5 C 3 E 0.5 G 0.5 O 3 C 1.5 O 2 B 0.25 O 3 C 0.25 O 2 B 1 A 0.5 G 0.5 A 0.5 O 3 C 0.5 O 2 E 0.5 D 0.5 C 1 C 0.5 C 0.5 C 0.5 O 3 C 1 O 2 G 0.25 A 0.25 G 0.5 D 0.25 E 0.25 D 0.5 C 0.25 D 0.25 D 3 E 0.5 G 0.5 O 3 C 1.5 O 2 B 0.25 O 3 C 0.25 O 2 B 1 A 0.5 G 0.5 A 0.5 O 3 C 0.5 O 2 E 0.5 D 0.5 C 1 C 0.5 C 0.5 C 0.5 O 3 C 1 O 2 G 0.25 A 0.25 G 0.5 D 0.25 E 0.25 D 0.5 C 0.5 C 3 "; Expression expression = null; try { while (context.PlayText.Length > 0) { string str = context.PlayText.Substring(0, 1); switch (str) { case "O": expression = new Scale(); break; case "T": expression = new Speed(); break; case "C": case "D": case "E": case "F": case "G": case "A": case "B": case "P": expression = new Note(); break; } expression.Interpret(context); } } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.Read(); } } //演奏内容 class PlayContext { //演奏文本 private string text; public string PlayText { get { return text; } set { text = value; } } } //表达式 abstract class Expression { //解释器 public void Interpret(PlayContext context) { if (context.PlayText.Length == 0) { return; } else { string playKey = context.PlayText.Substring(0, 1); context.PlayText = context.PlayText.Substring(2); double playValue = Convert.ToDouble(context.PlayText.Substring(0, context.PlayText.IndexOf(" "))); context.PlayText = context.PlayText.Substring(context.PlayText.IndexOf(" ") + 1); Excute(playKey, playValue); } } //执行 public abstract void Excute(string key, double value); } //音符 class Note : Expression { public override void Excute(string key, double value) { string note = ""; switch (key) { case "C": note = "1"; break; case "D": note = "2"; break; case "E": note = "3"; break; case "F": note = "4"; break; case "G": note = "5"; break; case "A": note = "6"; break; case "B": note = "7"; break; } Console.Write("{0} ", note); } } //音阶 class Scale : Expression { public override void Excute(string key, double value) { string scale = ""; switch (Convert.ToInt32(value)) { case 1: scale = "低音"; break; case 2: scale = "中音"; break; case 3: scale = "高音"; break; } Console.Write("{0} ", scale); } } //音速 class Speed : Expression { public override void Excute(string key, double value) { string speed; if (value < 500) speed = "快速"; else if (value >= 1000) speed = "慢速"; else speed = "中速"; Console.Write("{0} ", speed); } } }
.NET中的解释器模式
目前还不知道在.NET Framework类库中有解释器模式的应用。
效果及实现要点
优点:
将每一个语法规则表示成一个类,方便于实现语言。
因为语法由许多类表示,所以你可以轻易地改变或扩展此语言。
通过在类结构中加入新的语法,可以在解释的同时增加新的行为,如美化输出格式或进行复杂的程序验证。
用途:
需要实现一个简单的语言时,可以使用解释器。
有一个简单的语法,且简单比效率更重要时,使用解释器。
可以处理脚本语言或编程语言。
缺点:
当语法规则的数目太大时,由于为文法中的每一条规则需要定义至少一个类,所以这个模式可能会变得非常复杂。在这种情况下,使用解释器/编译器的产生器可能更合适。
使用解释器模式,可以很容易地改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。
总结
解释器模式不像其他模式一样使用范围那么广,但是在恰当的地方合理的使用解释器模式,起到的作用是四两拨千斤的。