27.2解释器模式

解释器模式(interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
比如判断Email、匹配电话号码等等,与其为每一个特定需求都写一个算法函数,不如使用一种通用的搜索算法来解释执行一个正则表达式,该正则表达式定义了待匹配字符串的集合。
而所谓的解释器模式,正则表达式就是它的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
解释器模式(interpreter)结构图 第二十七章 其实你不懂老板的心——解释器模式 - 图1代码示例
AbstractExpression(抽象表达式),声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。

  1. abstract class AbstractExpression
  2. {
  3. abstract function Interpret($context);
  4. }

TerminalExpression(终结符表达式),实现与文法中终结符相关联的解释操作。实现抽象表达式中所要求的接口,主要是一个interpret()方法。文法中每一个终结符都有一个具体终结表达式与之相对应。

  1. class TerminalExpression extends AbstractExpression
  2. {
  3. public function Interpret($context)
  4. {
  5. echo '终端解释器' . PHP_EOL;
  6. }
  7. }

NonterminalExpression(非终结符表达式),为文法中的非终结符实现解释操作。对文法中每一条规则R1、R2……Rn都需要一个具体的非终结符表达式类。通过实现抽象表达式的interpret()方法实现解释操作。解释操作以递归方式调用上面所提到的代表R1、R2……Rn中各个符号的实例变量。

  1. class NonTerminalExpression extends AbstractExpression
  2. {
  3. public function Interpret($context)
  4. {
  5. echo '非终端解释器' . PHP_EOL;
  6. }
  7. }

Context,包含解释器之外的一些全局信息。

  1. class Context
  2. {
  3. private $input = '';
  4. public function setInput($input)
  5. {
  6. $this->input = $input;
  7. }
  8. public function getInput()
  9. {
  10. return $this->input;
  11. }
  12. private $output = '';
  13. public function setOutput($output)
  14. {
  15. $this->output = $output;
  16. }
  17. public function getOutput()
  18. {
  19. return $this->output;
  20. }
  21. }

客户端代码,构建表示该文法定义的语言中一个特定的句子的抽象语法树。调用解释操作。

  1. public function interpreterDemo()
  2. {
  3. $context = new Context();
  4. $list = array();
  5. $list[] = new TerminalExpression();
  6. $list[] = new NonTerminalExpression();
  7. $list[] = new TerminalExpression();
  8. $list[] = new TerminalExpression();
  9. foreach ($list as $value) {
  10. $value->Interpret($context);
  11. }
  12. }

结果显示

  1. 终端解释器
  2. 非终端解释器
  3. 终端解释器
  4. 终端解释器

27.3解释器模式好处

通常当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
解释器模式的好处:
用了解释器模式,就意味着可以很容易地改变和扩展文法,因为该模式使用类类表示文法规则,你可使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。
解释器模式的不足:
解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使用其他的技术如语法分析程序或编译器生成器来处理。

27.5音乐解释器实现

代码结构图 第二十七章 其实你不懂老板的心——解释器模式 - 图2代码实现
演奏内容类(context)

  1. //演奏内容
  2. class PlayContext
  3. {
  4. //演奏文本
  5. private $text = '';
  6. public function setPlayText($text)
  7. {
  8. $this->text = $text;
  9. }
  10. public function getPlayText()
  11. {
  12. return $this->text;
  13. }
  14. }

表达式类(AbstractExpression)

  1. abstract class Expression
  2. {
  3. //解释器
  4. public function Interpret($context)
  5. {
  6. if (strlen($context->getPlayText()) == 0) {
  7. return ;
  8. } else {
  9. $play_text = trim($context->getPlayText());
  10. $play_text_arr = explode(' ', $play_text);
  11. $playKey = $play_text_arr[0];
  12. $playValue = $play_text_arr[1];
  13. unset($play_text_arr[0]);
  14. unset($play_text_arr[1]);
  15. $text = ' ';
  16. foreach ($play_text_arr as $value) {
  17. $text .= $value . ' ';
  18. }
  19. $context->setPlayText($text);
  20. $this->Execute($playKey, $playValue);
  21. }
  22. }
  23. //执行
  24. abstract function Execute($key, $value);
  25. }

音符类(TerminalExpression)

  1. class Note extends Expression
  2. {
  3. public function Execute($key, $value)
  4. {
  5. $note = '';
  6. switch ($key) {
  7. case 'C':
  8. $note = '1';
  9. break;
  10. case 'D':
  11. $note = '2';
  12. break;
  13. case 'E':
  14. $note = '3';
  15. break;
  16. case 'F':
  17. $note = '4';
  18. break;
  19. case 'G':
  20. $note = '5';
  21. break;
  22. case 'A':
  23. $note = '6';
  24. break;
  25. case 'B':
  26. $note = '7';
  27. break;
  28. }
  29. echo $note . ' ';
  30. }
  31. }

音阶类(TerminalExpression)

  1. class Scale extends Expression
  2. {
  3. public function Execute($key, $value)
  4. {
  5. $scale = '';
  6. switch ((int)$value) {
  7. case 1:
  8. $scale = '低音';
  9. break;
  10. case 2:
  11. $scale = '中音';
  12. break;
  13. case 3:
  14. $scale = '高音';
  15. break;
  16. }
  17. echo $scale . ' ';
  18. }
  19. }

音速类(TerminalExpression)

  1. class Speed extends Expression
  2. {
  3. public function Execute($key, $value)
  4. {
  5. $speed = '';
  6. if ($value < 500) {
  7. $speed = '快速';
  8. } else if ($value >= 1000) {
  9. $speed = '慢速';
  10. } else {
  11. $speed = '中速';
  12. }
  13. echo $speed . ' ';
  14. }
  15. }

客户端代码

  1. public function interpreterImp()
  2. {
  3. $context = new PlayContext();
  4. echo '上海滩:' . PHP_EOL;
  5. $text = ' 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 ';
  6. $context->setPlayText($text);
  7. try {
  8. while (strlen($context->getPlayText()) > 0 && $context->getPlayText() != ' ') {
  9. $str = substr($context->getPlayText(), 1, 1);
  10. switch ($str) {
  11. case 'O':
  12. $expression = new Scale();
  13. break;
  14. case 'T':
  15. $expression = new Speed();
  16. break;
  17. case 'C':
  18. case 'D':
  19. case 'E':
  20. case 'F':
  21. case 'G':
  22. case 'A':
  23. case 'B':
  24. case 'P':
  25. $expression = new Note();
  26. break;
  27. }
  28. $expression->Interpret($context);
  29. }
  30. } catch (\Exception $e) {
  31. echo $e->getMessage();
  32. }
  33. }

结果显示

  1. 上海滩:
  2. 中速 中音 3 5 6 3 5 2 3 5 6 高音 1 中音 6 5 1 3 2

客户端可以应用简单工厂加反射来消除当增加一个文法时对客户端的修改。
这个例子并不能代表解释器模式的全貌,因为它只有终结符表达式,而没有非终结符表达式的子类,如果箱真正理解解释器模式,还需要去研究其他的例子。