JAVA SCRIPT设计模式--行为型--设计模式之Interpreter解释器模式(15)

         JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能,所以不可能像C++,JAVA等面向对象语言一样严谨,大部分程序都附上了JAVA SCRIPT代码,代码只是实现了设计模式的主体功能,不代表全部的正确,特此声明。若读者需要了解设原则、设计变化方向,环境相关等信息请查看设计模式开篇

        所有JAVA SCRIPT设计模式快捷连接:

              创建型:(1)  抽象工厂 (2) 生成器 (3) 工厂方法 (4) 原型  (5) 单例

              结构型:(6) 适配器  (7) 桥接  (8) 组合 (9) 装饰 (10) 外观 (11) 享元 (12) 代理​

              行为型:(13) ​职责链 (14) ​命令 (15) ​解释器 (16) ​迭代器 (17) ​中介者 (18) ​备忘录 (119) ​观察者 (20) ​状态​ (21) ​策略 (22) ​模板方法 (23) 访问者​


一、UML类图

参与者:

1.1 AbstractExpression(抽象表达式,如RegularExpression)

  • 声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享

1.2 TerminalExpression(终结符表达式,如LiteralExpression)

  • 实现与文法中的终结符相关联的解释操作。
  • 一个句子中的每个终结符需要该类的一个实例。

1.3 NonterminalExpression(非终结符表达式,如AlternationExpression,RepetitionExpression,SequenceExpressions)

  • 对文法中的每一条规则R::=R1R2...Rn都需要一个NonterminalExpression类。
  • 为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量。
  • 为文法中的非终结符实现解释(Interpret)操作。解释(Interpret)一般要递归地调用表示R1到Rn的那些对象的解释操作。

1.4 Context(上下文)

  • —包含解释器之外的一些全局信息。

1.5 Client(客户)

  • 构建(或被给定)表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。
  • 调用解释操作。

二、意图

     给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子

三、适用性 

  1. the grammar is simple. For complex grammars, the class hierarchy forthe grammar becomes large and unmanageable. Tools such as parsergenerators are a better alternative in such cases. They can interpretexpressions without building abstract syntax trees, which can savespace and possibly time.语法很简单。对于复杂的语法 , 语法的类层次变得庞大而无法管理诸如解析器之类的工具是更好的选择。他们可以解释表达式,而无需构建抽象语法树,这可以节省空间,甚至可能节省时间。
  2. efficiency is not a critical concern. The most efficient interpretersare usually not implemented by interpreting parse trees directlybut by first translating them into another form. For example, regularexpressions are often transformed into state machines. But even then,the translator can be implemented by the Interpreter pattern, sothe pattern is still applicable. 效率不是一个关键问题。最高效的解释器通常不是通过直接解释语法分析树实现的 , 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种 情况下, 转换器仍可用解释器模式实现, 该模式仍是可用的。

四、优点和缺点

  1. 易于改变和扩展文法: 因为该模式使用类来表示文法规则 , 你可使用继承来改变或扩展该文法。已有的表达式可被增量式地改变 ,而新的表达式可定义为旧表达式的变体。
  2. 也易于实现文法: 定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可用一个编译器或语法分析程序生成器自动生成。
  3. 复杂的文法难以维护: 解释器模式为文法中的每一条规则至少定义了一个类(使用BNF定义的文法规则需要更多的类)。因此包含许多规则的文法可能难以管理和维护。可应用其他的设 计模式来缓解这一问题。但当文法非常复杂时, 其他的技术如语法分析程序或编译器生成器更为 合适。
  4. 增加了新的解释表达式的方式: 解释器模式使得实现新表达式“计算”变得容易。 例如, 你可以在表达式类上定义一个新的操作以支持优美打印或表达式的类型检查。如果你经常创建 新的解释表达式的方式, 那么可以考虑使用Visitor(5.11 )模式以避免修改这些代表文法的类。

五、示例代码

5.1  动机

         如果一种特定类型的问题发生的频率足够高 , 那么可能就值得将该问题的各个实例表述为 一个简单语言中的句子(then it might beworthwhile to express instances of the problem as sentences in a simple language)。这样就可以构建一个解释器 , 该解释器通过解释这些句子来解决该问题。

        例如,搜索匹配一个模式的字符串是一个常见问题。正则表达式是描述字符串模式的一 种标准语言。与其为每一个的模式都构造一个特定的算法,不如使用一种通用的搜索算法来解释执行一个正则表达式,该正则表达式定义了待匹配字符串的集合解释器模式描述了如何为简单的语言定义一个文法 , 如何在该语言中表示一个句子 , 以及如何解释这些句子。

        考虑以下文法定义正则表达式 :

expression ::= literal | alternation | sequence | repetition | 
'(' expression ')' 
alternation ::= expression '|' expression 
sequence ::= expression '&' expression 
repetition ::= expression '*' 
literal ::= 'a' | 'b' | 'c' | ... { 'a' | 'b' | 'c' | ... }* 

        符号expression是开始符号,literal是定义简单字的终结符。

        解释器模式使用类来表示每一条文法规则在规则右边的符号是这些类的实例变量。上面的文法用五个类表示:一个抽象类RegularExpression和它四个子类LiteralExpression(文字)、AlternationExpression(选择or)、SequenceExpression(sequ 跟随)和RepetitionExpression(重复)后三个类定义的变量代表子表达式。


        每个用这个文法定义的正则表达式都被表示为一个由这些类的实例构成的抽象语法树。例如,抽象语法树:

 
表示正则表达式:

raining&(dogs|cats)*

         如果我们为RegularExpression的每一子类都定义解释(Interpret)操作,那么就得到了为这些正则表达式的一个解释器。解释器将该表达式的上下文做为一个参数。上下文包含输入字符串和关于目前它已有多少已经被匹配等信息。为匹配输入字符串的下一部分,每一RegularExpression的子类都在当前上下文的基础上实现解释操作(Interpret)。例如

  • LiteralExpression将检查输入是否匹配它定义的字(literal)。
  • AlternationExpression将检查输入是否匹配它的任意一个选择项。
  • RepetitionExpression将检查输入是否含有多个它所重复的表达式

        等等。

        下面是Interpreter所要考虑的一些特殊问题:

  1. 创建抽象语法树解释器模式并未解释如何创建一个抽象的语法树。换言之,它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来生成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接由Client提供
  2. 定义解释操作并不一定要在表达式类中定义解释操作。如果经常要创建一种新的解释器,那么使用Visitor(5.11)模式将解释放入一个独立的“访问者”对象更好一些。例如,一个程序设计语言的会有许多在抽象语法树上的操作,比如类型检查、优化、代码生成,等等。恰当的做法是使用一个访问者以避免在每一个类上都定义这些操作
  3. 与Flyweight模式共享终结符在一些文法中,一个句子可能多次出现同一个终结符。此时最好共享那个符号的单个拷贝。计算机程序的文法是很好的例子—每个程序变量在整个代码中将会出现多次。在动机一节的例子中,一个句子中终结符dog(由LiteralExpression类描述)也可出现多次。

5.2  示例UML

目录结构:

5.2 AbstractExpression(抽象表达式,如RegularExpression)

  • 声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享

export default  class RegularExpression {
	 
    constructor( ) {
		 
    }
	/*解释
	 context :contains information that's global to the interpreter. 
	*/
    
	/**
	 * 实现了该正则表达式的一个解释器。定义抽象语法树的每一个类都实现了这一操作。
	 inputString 读入的部分输入字符串,返回object
	 */
	Match(context)
	{
		
	}
	 
  }

5.3 TerminalExpression(终结符表达式,如LiteralExpression)

  • 实现与文法中的终结符相关联的解释操作。
  • 一个句子中的每个终结符需要该类的一个实例。
import RegularExpression  from '../RegularExpression.js'; 
 
export default  class LiteralExpression extends RegularExpression {
   
    #components;
	constructor(components  ) {
	 super();
	 this.#components=components;
	}
   
	/*解释*/
	Match(context)
	{
		let inputString=context.GetInString();
		let arrayString=inputString.split(' ');//根据空格 获取token
		let sIndex=0;
		for(let n=0;n<arrayString.length;n++)
		{
			if(arrayString[n]==''){
				sIndex++
				continue;//过滤空字符
				}
		}
		if(sIndex<arrayString.length&&arrayString[sIndex]==this.#components)
		{
			context.SetMatchString(arrayString[sIndex]);
			context.SetInString(inputString.substr(arrayString[sIndex].length+sIndex)) ;
			return true
		}
		else
			return false;
	}
  }

5.4 NonterminalExpression(非终结符表达式,如AlternationExpression,RepetitionExpression,SequenceExpressions)

  • 对文法中的每一条规则R::=R1R2...Rn都需要一个NonterminalExpression类。
  • 为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量。
  • 为文法中的非终结符实现解释(Interpret)操作。解释(Interpret)一般要递归地调用表示R1到Rn的那些对象的解释操作。
import RegularExpression  from '../RegularExpression.js'; 
 
export default  class AlternationExpression extends RegularExpression {
	#altercative1;
	#altercative2;
	constructor( altercative1,altercative2 ) {
	 super();
	 this.#altercative1=altercative1;
	 this.#altercative2=altercative2;
	}
	/*解释*/
	 
	Match(context)
	{
		if(this.#altercative1.Match(context))
		{
			return true;
		}else
		{
			if(this.#altercative2.Match(context))
				return true;
			else{
				 
				return false;
			}
		}
	}
  }
import RegularExpression from '../RegularExpression.js';

export default class RepetitionExpression extends RegularExpression {
	#repetition;
	constructor(repetition) {
		super();
		this.#repetition = repetition;
	}

	/*解释*/
	Match(context) {
		let bOnceFind = false;
		do {
			if (this.#repetition.Match(context)) {
				bOnceFind = true;
			} else {
				//没有匹配到
				break;
			}
		} while (true);
		if(!bOnceFind) context.SetMatchStringNull();
		return bOnceFind; 
	}
}
import RegularExpression  from '../RegularExpression.js'; 
 
export default  class SequenceExpression extends RegularExpression {
   
    #expression1;
	#expression2;
	constructor( expression1,expression2 ) {
	 super();
	 this.#expression1=expression1;
	 this.#expression2=expression2;
	}
   
	/*解释*/
	Match(context)
	{
		if(this.#expression1.Match(context))
		{
			 if(this.#expression2.Match(context))
				return true;
			else
			{
				//没有匹配到
				context.SetMatchStringNull();
			    return false
			}
			  
		}else
			context.SetMatchStringNull();
			return false
		 
	}
  }

5.5 Context(上下文)

  • —包含解释器之外的一些全局信息。

5.6 Client(客户)

  • 构建(或被给定)表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。
  • 调用解释操作。
import AlternationExpression from './Expression/impl/AlternationExpression.js';
import LiteralExpression from './Expression/impl/LiteralExpression.js';
import RepetitionExpression from './Expression/impl/RepetitionExpression.js';
import SequenceExpression from './Expression/impl/SequenceExpression.js';
import Context from './Expression/Context.js';


export default class Client {

	constructor(ctx, zooRect) {

		/**
		 * 构建语法树
		 */
		let aLiteralExpression = new LiteralExpression('raining');
		let aLiteralExpressiondogs = new LiteralExpression('dogs');
		let aLiteralExpressioncats = new LiteralExpression('cats');
		let anAlternationExpression = new AlternationExpression(aLiteralExpressiondogs, aLiteralExpressioncats);
		let aRepetitionExpression = new RepetitionExpression(anAlternationExpression);
		let aSequenceExpression = new SequenceExpression(aLiteralExpression, aRepetitionExpression);

		/**
		 * 匹配字符串
		 */
		let aString = 'raining cats dogs dogs cat';
		let context=new Context(aString);
		console.log(` 原始字符串  `+aString +'匹配表达式 raining&(dogs|cats)*');
		aSequenceExpression.Match(context);
		console.log(` 匹配的字符串  `+context.GetMatchString());
		
		let bString = 'raining cats dog dogs cat';
		let bcontext=new Context(bString);
		console.log(` 原始字符串  `+bString +'匹配表达式 raining&(dogs|cats)*');
		aSequenceExpression.Match(bcontext);
		console.log(` 匹配的字符串  `+bcontext.GetMatchString());
		
		
		let cString = 'raining cat dogs dog cat';
		let ccontext=new Context(cString);
		console.log(` 原始字符串  `+cString +'匹配表达式 raining&(dogs|cats)*');
		aSequenceExpression.Match(ccontext);
		console.log(` 匹配的字符串  `+ccontext.GetMatchString());
		
	}


}

5.8 测试HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
 
 
 <script  type="module" >
 import Client  from './Client.js'; 
 var x=document.getElementById("mycanvas")
 var ctx=x.getContext("2d") //create 2d object
let cl=new Client();
cl.main(ctx)

 </script>
</head>
<body>
<canvas id="mycanvas" width=900px height=900px></canvas>
    
</body>
</html>

测试结果:

Client.js:27  原始字符串  raining cats dogs dogs cat匹配表达式 raining&(dogs|cats)*
Client.js:29  匹配的字符串   raining cats dogs dogs
Client.js:33  原始字符串  raining cats dog dogs cat匹配表达式 raining&(dogs|cats)*
Client.js:35  匹配的字符串   raining cats
Client.js:40  原始字符串  raining cat dogs dog cat匹配表达式 raining&(dogs|cats)*
Client.js:42  匹配的字符串  

六、源代码下载

        下载链接:https://pan.baidu.com/s/1XuPqp84cccBNVkbnMY3sKw 
         提取码:q2ut

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值