深入浅出基于Java的表明器设计模式
副标题#e#
一、引子
其实没有什么好的例子引入表明器模式,因为它描写了如何组成一个简朴的语言表明器,主要应用在利用面向工具语言开拓编译器中;在实际应用中,我们大概很少遇到去结构一个语言的文法的环境。
固然你险些用不到这个模式,可是看一看照旧能受到必然的开导的。
二、界说与布局
表明器模式的界说如下:界说语言的文法,而且成立一个表明器来表明该语言中的句子。它属于类的行为模式。这里的语言意思是利用划命名目和语法的代码。
在GOF的书中指出:假如一种特定范例的问题产生的频率足够高,那么大概就值得将该问题的各个实例表述为一个简朴语言中的句子。这样就可以构建一个表明器,该表明器通过表明这些句子来办理该问题。并且当文法简朴、效率不是要害问题的时候结果最好。
这也就是表明器模式应用的情况了。
让我们来看看神秘的表明器模式是由什么来构成的吧。
1) 抽象表达式脚色:声明一个抽象的表明操纵,这个接口为所有详细表达式脚色(抽象语法树中的节点)都要实现的。
什么叫做抽象语法树呢?《java与模式》中给的表明为:抽象语法树的每一个节点都代表一个语句,而在每个节点上都可以执行表明要领。这个表明要领的执行就代表这个语句被表明。由于每一个语句都代表这个语句被表明。由于每一个语句都代表一个常见的问题的实例,因此每一个节点上的表明操纵都代表对一个问题实例的解答。
2) 终结符表达式脚色:详细表达式。
a) 实现与文法中的终结符相关联的表明操纵
b) 并且句子中的每个终结符需要该类的一个实例与之对应
3) 非终结符表达式脚色:详细表达式。
a) 文法中的每条法则R::=R1R2…Rn都需要一个非终结符表带式脚色
b) 对付从R1到Rn的每个标记都维护一个抽象表达式脚色的实例变量
c) 实现表明操纵,表明一般要递归地挪用暗示从R1到Rn的那些工具的表明操纵
4) 上下文(情况)脚色:包括表明器之外的一些全局信息。
5) 客户脚色:
a) 构建(可能被给定)暗示该文法界说的语言中的一个特定的句子的抽象语法树
b) 挪用表明操纵
放上张表明器布局类图吧,这也是来自于GOF的书中。
对每一个脚色都给出了具体的职责,并且在类图中给出五个脚色之间的干系。这样实现起来也不是很坚苦了,下面举了一个简朴的例子,但愿能加深你对表明器模式的领略。
#p#副标题#e#
三、举例
来举一个加减乘除的例子吧,实现思路来自于《java与模式》中的例子。每个脚色的成果凭据上面提到的类型来实现。
//上下文(情况)脚色,利用HashMap来存储变量对应的数值
class Context
{
private Map valueMap = new HashMap();
public void addValue(Variable x , int y)
{
Integer yi = new Integer(y);
valueMap.put(x , yi);
}
public int LookupValue(Variable x)
{
int i = ((Integer)valueMap.get(x)).intValue();
return i ;
}
}
//抽象表达式脚色,也可以用接口来实现
abstract class Expression
{
public abstract int interpret(Context con);
}
//终结符表达式脚色
class Constant extends Expression
{
private int i ;
public Constant(int i)
{
this.i = i;
}
public int interpret(Context con)
{
return i ;
}
}
class Variable extends Expression
{
public int interpret(Context con)
{
//this为挪用interpret要领的Variable工具
return con.LookupValue(this);
}
}
//非终结符表达式脚色
class Add extends Expression
{
private Expression left ,right ;
public Add(Expression left , Expression right)
{
this.left = left ;
this.right= right ;
}
public int interpret(Context con)
{
return left.interpret(con) + right.interpret(con);
}
}
class Subtract extends Expression
{
private Expression left , right ;
public Subtract(Expression left , Expression right)
{
this.left = left ;
this.right= right ;
}
public int interpret(Context con)
{
return left.interpret(con) - right.interpret(con);
}
}
class Multiply extends Expression
{
private Expression left , right ;
public Multiply(Expression left , Expression right)
{
this.left = left ;
this.right= right ;
}
public int interpret(Context con)
{
return left.interpret(con) * right.interpret(con);
}
}
class Division extends Expression
{
private Expression left , right ;
public Division(Expression left , Expression right)
{
this.left = left ;
this.right= right ;
}
public int interpret(Context con)
{
try{
return left.interpret(con) / right.interpret(con);
}catch(ArithmeticException ae)
{
System.out.println("被除数为0!");
return -11111;
}
}
}
//测试措施,计较 (a*b)/(a-b+2)
public class Test
{
private static Expression ex ;
private static Context con ;
public static void main(String[] args)
{
con = new Context();
//配置变量、常量
Variable a = new Variable();
Variable b = new Variable();
Constant c = new Constant(2);
//为变量赋值
con.addValue(a , 5);
con.addValue(b , 7);
//运算,对句子的布局由我们本身来阐明,结构
ex = new Division(new Multiply(a , b), new Add(new Subtract(a , b) , c));
System.out.println("运算功效为:"+ex.interpret(con));
}
}
#p#分页标题#e#
表明器模式并没有说明如何建设一个抽象语法树,因此它的实现可以多种多样,在上面我们是直接在Test中提供的,虽然尚有更好、更专业的实现方法。
对付终结符,GOF发起回收享元模式来共享它们的拷贝,因为它们要多次反复呈现。可是思量到享元模式的利用范围性,我发起照旧当你的系统中终结符反复的足够多的时候再思量享元模式。
四、优缺点
表明器模式提供了一个简朴的方法来执行语法,并且容易修改可能扩展语法。一般系统中许多类利用相似的语法,可以利用一个表明器来取代为每一个法则实现一个表明器。并且在表明器中差异的法则是由差异的类来实现的,这样使得添加一个新的语礼貌则变得简朴。
可是表明器模式对付巨大文法难以维护。可以想象一下,每一个法则要对应一个处理惩罚类,并且这些类还要递归挪用抽象表达式脚色,多如乱麻的类交叉在一起是何等可怕的一件事啊!
五、总结
这样对表明器模式应该有了些概略的认识了吧,由于这个模式利用的案例匮乏,所以本文大部门概念直接来自于GOF的原著。只是实例代码是亲自实现并调试通过的。