这篇文章来学习一下解释器模式,然后实现一个音乐解释器的例子。
解释器其实就是用来翻译文法句子的。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言的句子,这样就可以构建一个解释器。该解释器通过解释这些句子来解决该问题。
比方说正则表达式,我们常常会在字符串中搜索匹配的字符或判断一个字符串是否符合我们规定的格式,就会用到它。
解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
像IE这些浏览器,其实也是在解释HTML文法,将下载到客户端的HTML标记文本转换成网页格式显示到用户。
解释器模式有什么好处呢?
当有个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
用了解释器模式,就意味着可以容易的改变和扩展文法,因为该模式使用类来表示文法,你可使用继承来改变或扩展该文法,也比较容易实现文法。因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。
解释器模式有不足的,解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护,建议将文法非常复杂时,使用其他的技术如语法分析程序或编译器生成器来处理。
我们通过一个音乐解释器来学习一下这个模式。
定义一套规则,O表示音阶, O 1表示低音阶, O 2 表示中音阶, O 3 表示高音阶, P表示休止符, CDEFGAB表示 Do-Re-Mi-Fa-So-La-Ti 音符长度 1表示一拍, 2表示二拍,0.5表示半拍,0.25表示四分之一拍,以此类推。注意,所有的字母和数字都要用半角空格分开。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Interpreter
{
//演奏内容
class PlayContent
{
//演奏文本
private string text;
public string PlayText
{
get { return text; }
set { text = value; }
}
}
abstract class Expression
{
//解释器
public void Interpret(PlayContent content)
{
if (content.PlayText.Length == 0)
return;
else
{
//此方法用于将当前的演奏文本第一条命令获得命令字母和其参数值
//获得key和value后将其从演奏文本移除
string key = content.PlayText.Substring(0, 1);
content.PlayText = content.PlayText.Substring(2);
double value = double.Parse(content.PlayText.Substring(0, content.PlayText.IndexOf(" ")));
content.PlayText = content.PlayText.Substring(content.PlayText.IndexOf(" ") + 1);
Excute(key, value);
}
}
//抽象方法执行,不同的文法子类,有不同的执行处理。
public abstract void Excute(string key, double value);
}
//音符类
class Note : Expression
{
public override void Excute(string key, double value)
{
//如果获得的key是C则演奏1(do),如果是D则演奏2(Re)
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))
{
//如果获得的key是O并且value是1 则演奏低音,2则是中音, 3则是高音
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);
}
}
class Program
{
static void Main(string[] args)
{
PlayContent content = new PlayContent();
content.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("上海滩: ");
Expression expression = null;
try
{
while (content.PlayText.Length > 0)
{
string str = content.PlayText.Substring(0, 1);
switch (str)
{
//当首字段是O时,则表达式实例化为音阶
case "O":
expression = new Scale();
break;
//增加对T的判断
case "T":
expression = new Speed();
break;
case "C":
case "D":
case "E":
case "F":
case "G":
case "A":
case "B":
case "P":
//当首字母是CDEFGAB,以及休止符P时,则实例化音符
expression = new Note();
break;
}
expression.Interpret(content);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
}
}
如果增加一个文法时,除了扩展一个类外,还是改动了客户端。
只要在客户端的switch那里引用简单工厂加反射就可做到不改动客户端了。
其实这个例子是不能代表解释器模式的全貌的,因为它只有终结符表达式,而没有非终结符表达式的子类,如果想真正理解解释器模式,还需要去研究其他的例子。