1、写在前面
这个模式是特意放在最后才写出来,因为我看了3遍也没有完全理解这个模式的含义,这里只能是暂时按照书中的定义挪过来了。其实可以粗略的说像IE等浏览器其实也是在解释HTML文法,将客户端传来的HTML标记文本转换成相应的网页格式展示给用户(当然,浏览器程序自身还有很多其他复杂的逻辑,这里只是做一个大概的类比来理解一下解释器程序)。
2、定义
- 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
- 所谓的解释器模式,正则表达式就是他的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
- 当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,就可以使用解释器模式。
3、优缺点及适用处
优点:很容易的改变和扩展文法,因为解释器模式使用类来表示文法规则,你可以使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。
缺点:解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。所以建议,当文法非常复杂时,使用其他的技术如语法分析程序、编译器生成器来处理。
解释器模式需要解决的是这样一类问题:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例标书为一个简单语言中的一个句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。举例来说:针对机器人,如果让他走一段路还需要调用向前、左转、右转的方法就太不合适了,应该可以直接对它说“向前走10步,然后左转,在向右走5步”,而解释器模式在这里就是将这样的一句话转变成实际的命令程序执行而已。
从浅显的层面来说,用解释器模式就相当于你开发了一个编程语言或脚本给自己或别人用。也就是说,解释器模式就是用‘迷你语言’来表现程序需要解决的问题,以迷你语言写成‘迷你程序’来表现具体的问题。
4、实例代码
这里以一个模拟音乐解释器的代码来作为实例,具体如下,定义一套规则:
* O表示音阶:‘O1’表示低音阶,‘O2’表示中音阶,‘O3’表示高音阶;
* P表示休止符,‘C D E F G A B’表示‘Do-Re-Mi-Fa-So-La-Ti’;
* 音符长度:1表示一拍,2表示二拍,0.5表示半拍,0.25表示四分之一拍,以此类推
* 注意:所有的字母和数字之间都要用半角空格分开。
* 例如:上海滩的第一句,“浪奔”可以写成[O 2 E 0.5 G 0.5 A 3],表示中音开始,演奏的是mi so la
首先,定义一个演奏内容类,存储获取的文法:
/// <summary> /// 演奏内容类 /// </summary> class PlayContext { string mc_text; public string PlayText { get { return mc_text; } set { mc_text = value; } } }
其次,定义表达式类,负责解释每个文法的具体含义,并执行解释后的文法:
/// <summary> /// 表达式类 /// </summary> abstract class Expression { /// <summary> /// 解释器 /// </summary> /// <param name="pContextObj">演奏内容对象</param> public void Interpret(PlayContext pContextObj) { if (pContextObj.PlayText.Length == 0) return; else { //用于将当前演奏文本第一条命令获得命令字母值和参数值 //例如“O 3 E 0.5 G 0.5 A 3”则playkey为O,playvalue为3 string lcPlayKey = pContextObj.PlayText.Substring(0,1); pContextObj.PlayText = pContextObj.PlayText.Substring(2); //int tmp = pContextObj.PlayText.IndexOf(" "); 检索待查询字符在字符串中第一次出现位置的索引 double lcPlayValue = Convert.ToDouble(pContextObj.PlayText.Substring(0,pContextObj.PlayText.IndexOf(" "))); //获得playkey和playvalue后将其从演奏文本中移除 //如上面的例子,变成了“E 0.5 G 0.5 A 3” pContextObj.PlayText = pContextObj.PlayText.Substring(pContextObj.PlayText.IndexOf(" ")+1); Excute(lcPlayKey,lcPlayValue); } } //演奏 public abstract void Excute(string pKey,double pValue); }
然后,定义具体的表达式类,这些具体表达式继承自表达式抽象类:
/// <summary> /// 音符类 /// </summary> class Note:Expression { public override void Excute(string pKey, double pValue) { string lcNote = ""; switch(pKey) { case "C": lcNote = "1"; break; case "D": lcNote = "2"; break; case "E": lcNote = "3"; break; case "F": lcNote = "4"; break; case "G": lcNote = "5"; break; case "A": lcNote = "6"; break; case "B": lcNote = "7"; break; } Console.Write("{0} ",lcNote); } } /// <summary> /// 音阶类 /// </summary> class Scale : Expression { public override void Excute(string pKey, double pValue) { string lcScale = ""; switch(Convert.ToInt32(pValue)) { case 1: lcScale = "低音"; break; case 2: lcScale = "中音"; break; case 3: lcScale = "高音"; break; } Console.Write("{0} ",lcScale); } }
最后,在客户端写入一句文法,然后解释并执行:
class Program { static void Main(string[] args) { //初始化演奏内容 PlayContext lcContext = new PlayContext(); Console.WriteLine("开始演奏:上海滩"); lcContext.PlayText = @"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 "; //根据不同文法初始化解释器 Expression lcExpression = null; try { while(lcContext.PlayText.Length>0) { string tmpContext = lcContext.PlayText.Substring(0,1); switch(tmpContext) { case "O": lcExpression = new Scale(); break; case "C": case "D": case "E": case "F": case "G": case "A": case "B": case "P": lcExpression = new Note(); break; } //解释具体文法,并在内部执行解释结果 lcExpression.Interpret(lcContext); } } catch(Exception error) { Console.WriteLine(error.Message); } Console.ReadKey(); } }
5、结束语
上面是一个最简化的解释模式实例,当然也并没有把解释模式完全表现出来,因为在上面的例子中只有终结符表达式,而没有非终结符表达式的子类,所以如果想真正理解解释器模式,还需要去研究其他例子。另外,这个模式我在抄完这些代码后其实也没有完全理解,因为确实没有在实际中碰到使用这个模式的具体代码,所以暂时只能这么浅显的理解一下这个模式。功夫还是没下到啊……