设计模式-行为型-解释器模式
解释器模式(interpreter):
从名称上来看看这个模式,个人的最初理解“解释器”和google的中英翻译功能类似。如果有一天你去国外旅游去了,比如去美国吧,美国人是讲英语的,我们是讲汉语的,如果英语听不懂,讲不好,估计沟通就完蛋了,不能沟通,估计玩的就很难尽兴了,因为有很多景点的解说你可能不明白(没有中文翻译的情况下,一般情况会有的)。所以我们需要一个软件,可以把中英文互译,那彼此就可以更好的理解对方的意思,我感觉翻译软件也可以称得上是解释器,把你不懂的解释成你能理解的。我们写代码,需要编译器把我们写的代码编译成机器可以理解的机器语言,从这方面来讲,c#的编译器也是一种解释器。
解释器模式的角色:
1)抽象解释器(abstractexpression):定义解释器的接口,约定解释器的解释操作。其中的interpret接口,正如其名字那样,它是专门用来解释该解释器所要实现的功能。
2)终结符表达式(termialexpression):实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式r=r1+r2,在里面r1和r2就是终结符,对应的解析r1和r2的解释器就是终结符表达式。
3)非终结符表达式(nonterminalexpression):文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式r=r1+r2中,“+”就是非终结符,解析“+”的解释器就是一个非终结符表达式。
4)环境角色(context):这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如r=r1+r2,我们给r1赋值100,给r2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用map来充当环境角色就足够了。
我们演示一个判断且或的例子。
1 public abstract class abstractexpression 2 { 3 public abstract bool interpret(string context); 4 } 5 6 public class terminalexpression : abstractexpression 7 { 8 private string data; 9 10 public terminalexpression(string data) 11 { 12 this.data = data; 13 } 14 15 public override bool interpret(string context) 16 { 17 return data.contains(context); 18 } 19 } 20 21 public class orexpression : abstractexpression 22 { 23 private abstractexpression expr1 = null; 24 private abstractexpression expr2 = null; 25 26 public orexpression(abstractexpression expr1, abstractexpression expr2) 27 { 28 this.expr1 = expr1; 29 this.expr2 = expr2; 30 } 31 32 public override bool interpret(string context) 33 { 34 return expr1.interpret(context) || expr2.interpret(context); 35 } 36 } 37 38 public class andexpression : abstractexpression 39 { 40 private abstractexpression expr1 = null; 41 private abstractexpression expr2 = null; 42 43 public andexpression(abstractexpression expr1, abstractexpression expr2) 44 { 45 this.expr1 = expr1; 46 this.expr2 = expr2; 47 } 48 49 public override bool interpret(string context) 50 { 51 return expr1.interpret(context) || expr2.interpret(context); 52 } 53 } 54 55 public class program 56 { 57 //规则:robert 和 john 是男性 58 public static abstractexpression getmaleexpression() 59 { 60 abstractexpression robert = new terminalexpression("robert"); 61 abstractexpression john = new terminalexpression("john"); 62 return new orexpression(robert, john); 63 } 64 65 //规则:julie 是一个已婚的女性 66 public static abstractexpression getmarriedwomanexpression() 67 { 68 abstractexpression julie = new terminalexpression("julie"); 69 abstractexpression married = new terminalexpression("married"); 70 return new andexpression(julie, married); 71 } 72 73 public static void main(string[] args) 74 { 75 abstractexpression ismale = getmaleexpression(); 76 abstractexpression ismarriedwoman = getmarriedwomanexpression(); 77 78 console.writeline($"john is male? {ismale.interpret("john")}"); 79 console.writeline($"julie is a married women? {ismarriedwoman.interpret("married julie")}"); 80 } 81 }
这里我们可以得出:解释器模式有很好的扩展模式,或此时我们希望能够找到一位男士已婚,我们只需要再写一个非终结符表达式即可,易于扩展。我们再来看下面这个例子。
1 // 抽象表达式 2 public abstract class expression 3 { 4 protected dictionary<string, int> table = new dictionary<string, int>(9); 5 6 protected expression() 7 { 8 table.add("一", 1); 9 table.add("二", 2); 10 table.add("三", 3); 11 table.add("四", 4); 12 table.add("五", 5); 13 table.add("六", 6); 14 table.add("七", 7); 15 table.add("八", 8); 16 table.add("九", 9); 17 } 18 19 public virtual void interpreter(context context) 20 { 21 if (context.statement.length == 0) 22 { 23 return; 24 } 25 26 foreach (string key in table.keys) 27 { 28 int value = table[key]; 29 30 if (context.statement.endswith(key + getpostfix())) 31 { 32 context.data += value * this.multiplier(); 33 context.statement = context.statement.substring(0, context.statement.length - this.getlength()); 34 } 35 if (context.statement.endswith("零")) 36 { 37 context.statement = context.statement.substring(0, context.statement.length - 1); 38 } 39 } 40 } 41 42 public abstract string getpostfix(); 43 44 public abstract int multiplier(); 45 46 //这个可以通用,但是对于个位数字例外,所以用虚方法 47 public virtual int getlength() 48 { 49 return this.getpostfix().length + 1; 50 } 51 } 52 53 //个位表达式 54 public sealed class geexpression : expression 55 { 56 public override string getpostfix() 57 { 58 return ""; 59 } 60 61 public override int multiplier() 62 { 63 return 1; 64 } 65 66 public override int getlength() 67 { 68 return 1; 69 } 70 } 71 72 //十位表达式 73 public sealed class shiexpression : expression 74 { 75 public override string getpostfix() 76 { 77 return "十"; 78 } 79 80 public override int multiplier() 81 { 82 return 10; 83 } 84 } 85 86 //百位表达式 87 public sealed class baiexpression : expression 88 { 89 public override string getpostfix() 90 { 91 return "百"; 92 } 93 94 public override int multiplier() 95 { 96 return 100; 97 } 98 } 99 100 //千位表达式 101 public sealed class qianexpression : expression 102 { 103 public override string getpostfix() 104 { 105 return "千"; 106 } 107 108 public override int multiplier() 109 { 110 return 1000; 111 } 112 } 113 114 //万位表达式 115 public sealed class wanexpression : expression 116 { 117 public override string getpostfix() 118 { 119 return "万"; 120 } 121 122 public override int multiplier() 123 { 124 return 10000; 125 } 126 127 public override void interpreter(context context) 128 { 129 if (context.statement.length == 0) 130 { 131 return; 132 } 133 134 arraylist tree = new arraylist(); 135 136 tree.add(new geexpression()); 137 tree.add(new shiexpression()); 138 tree.add(new baiexpression()); 139 tree.add(new qianexpression()); 140 141 foreach (string key in table.keys) 142 { 143 if (context.statement.endswith(getpostfix())) 144 { 145 int temp = context.data; 146 context.data = 0; 147 148 context.statement = context.statement.substring(0, context.statement.length - this.getlength()); 149 150 foreach (expression exp in tree) 151 { 152 exp.interpreter(context); 153 } 154 context.data = temp + context.data * this.multiplier(); 155 } 156 } 157 } 158 } 159 160 //亿位表达式 161 public sealed class yiexpression : expression 162 { 163 public override string getpostfix() 164 { 165 return "亿"; 166 } 167 168 public override int multiplier() 169 { 170 return 100000000; 171 } 172 173 public override void interpreter(context context) 174 { 175 arraylist tree = new arraylist(); 176 177 tree.add(new geexpression()); 178 tree.add(new shiexpression()); 179 tree.add(new baiexpression()); 180 tree.add(new qianexpression()); 181 182 foreach (string key in table.keys) 183 { 184 if (context.statement.endswith(getpostfix())) 185 { 186 int temp = context.data; 187 context.data = 0; 188 context.statement = context.statement.substring(0, context.statement.length - this.getlength()); 189 190 foreach (expression exp in tree) 191 { 192 exp.interpreter(context); 193 } 194 context.data = temp + context.data * this.multiplier(); 195 } 196 } 197 } 198 } 199 200 //环境上下文 201 public sealed class context 202 { 203 private string _statement; 204 private int _data; 205 206 public context(string statement) 207 { 208 this._statement = statement; 209 } 210 211 public string statement 212 { 213 get { return this._statement; } 214 set { this._statement = value; } 215 } 216 217 public int data 218 { 219 get { return this._data; } 220 set { this._data = value; } 221 } 222 } 223 224 internal class program 225 { 226 private static void main(string[] args) 227 { 228 string roman = "五亿七千三百零二万六千四百五十二"; 229 //分解:((五)亿)((七千)(三百)(零)(二)万) 230 //((六千)(四百)(五十)(二)) 231 232 context context = new context(roman); 233 list<expression> tree = new list<expression>(); 234 tree.add(new geexpression()); 235 tree.add(new shiexpression()); 236 tree.add(new baiexpression()); 237 tree.add(new qianexpression()); 238 tree.add(new wanexpression()); 239 tree.add(new yiexpression()); 240 241 foreach (expression exp in tree) 242 { 243 exp.interpreter(context); 244 } 245 246 console.write(context.data); 247 } 248 }
看完之后是不是想骂一句fuck,我只是想要简单的转换一下,却需要写这么一大坨,显然不符合我们的心意。
解释器模式的优缺点:
优点:
1)易于改变和扩展文法。
2)每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
3)实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
4)增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“”。
缺点:
1)对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
2)执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
解释器模式的应用场景:
1)当一个语言需要解释执行,并可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式(如xml文档解释、正则表达式等领域)。
2)一些重复出现的问题可以用一种简单的语言来进行表达。
3)一个语言的文法较为简单.
4)当执行效率不是关键和主要关心的问题时可考虑解释器模式(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。)
上一篇: 线程的来龙去脉,你了解吗?