引言
DSL(domain-specific language)并不是什么新的概念和技术,但是目前它已成为了一个技术热点,近期各种类型的技术交流或研讨会上你都可以看到关于DSL的主题。DSL似乎也在一夜间成为了大师们关注的焦点(Martin Fowler,Eric Evans等等)。
应用DSL可以有效的提高系统的可维护性(缩小了实现模型和领域模型的距离,提高了实现的可读性)和灵活性,并且提供开发的效率。
那么如何在我们的实践中引入DSL呢,Martin Fowler就DSL实现模式做了全面的阐释;在实际工作中作者实践了部分Martin Fowler的模式,下文是作者对这些实践的经验总结,愿与大家分享。
根据实现方式的分类DSL可以大致分为内部DSL(Internal DSL)和外部DSL(Extern DSL), 作者在实际项目中实践了这两大类DSL,在系列文章中将分别共享各类型DSL的实现经验。
示例涉及的模型
为了便于说明问题,系列文章讲围绕一个简单得示例展开,将以不同方式实现一个关于状态机描述的DSL。
Figure 1状态机
Figure 2 领域模型
实现DSL的本质任务
无论是实现内部DSL或是外部DSL,要完成的本质任务就是将DSL API调用及DSL语言脚本解析为应用中的语义模型(通常为应用中领域模型的部分)。
实现DSL
实现内部DSL
内部DSL实际上就是一组精心设计的API,同时这种API及其对他的调用有着某些特定领域中自然语言的特性。
以下分享两种内部DSL实现方法的实现经验。
实现要点
不要将这种DSL API直接放置在领域模型中,这不符合关注点分离的思想,并且导致代码难以维护,应该引入一个新的层次——Builder层,由Builder层来解析调用并创建为系统中的语义模型对象。
几种模式的实现要点
方法链(Method Chain)
调用示例
publicclass Client {
publicstaticvoid main(String [] args){
Process p=new Process();
ProcessBuilder process=new ProcessBuilder(p);
process.name("Auto-Door")
.state("Open")
.transition()
.event("timeout")
.nextState("close")
.state("Close")
.transition()
.event("people-closer")
.nextState("open");
System.out.println(p);
}
}
实现
ProcessBuilder.java:
publicclass ProcessBuilder {
protected Process process;
public ProcessBuilder(Process process2){
this.process=process2;
}
public ProcessBuilder name(String name){
process.setName(name);
returnthis;
}
public StateBuilder state(String name){
State newState=new State();
StateBuilder sb= new StateBuilder(process,newState);
sb.name(name);
process.getStates().add(newState);
return sb;
}
}
StateBuilder.java:
public class StateBuilder extends ProcessBuilder{
protected State state=new State();
public StateBuilder(Process p,State state){
super(p);
this.state=state;
}
public StateBuilder name(String name){
state.setName(name);
returnthis;
}
public TransitionBuilder transition(){
Transition t=new Transition();
TransitionBuilder tb= new TransitionBuilder(process,state,t);
state.getTransitions().add(t);
return tb;
}
}
TransitionBuilder.java
publicclass TransitionBuilder extends StateBuilder {
private Transition transition;
public TransitionBuilder(Process process,State state,Transition transition){
super(process,state);
this.transition=transition;
}
public TransitionBuilder event(String event){
transition.setEvent(new Event(event));
returnthis;
}
public TransitionBuilder nextState(String state){
returnthis;
}
}
实现要点
1 返回值
每个方法的返回都是Builder,这是为了可以实现这种链式调用。
2 继承
可以发现下一层次的Builder仍需可以提供上面层次Builder中的方法,我们把方法链拉直你便一目了然了。
由此可见为了避免代码重复,可以采用继承机制,让下层的Builder继承自上层的Builder。
3 上下文变量(context variable)
从代码中我们可以发现这些变量(各个Builder中的成员变量,process,state,tranistion),他们用来保证我们的Builder是在正确的上下文上工作,如把生成的transition加入到正确的state中。
这些上文变量在不同Builder将是通过构造函数进行传递的。
嵌套方法(Nested function)
调用示例
publicstaticvoid main(String[] args) {
Process p=process("Auto-Door", new State []{
state("Open",new Transition[]{
transition(
event("timeour"),
nextState("Close")
)
}),
state("Close",new Transition[]{
transition(
event("people-closer "),
nextState("Open")
)
})
});
}
实现
Builder.java
publicclass Builder {
publicstatic Process process(String name, State [] states){
Process process=new Process();
process.setName(name);
List<State> sts=new ArrayList<State>();
for (State s:states){
sts.add(s);
}
process.setStates(sts);
return process;
}
publicstatic State state(String name,Transition [] transitions){
State state=new State();
state.setName(name);
List<Transition> ts=new ArrayList<Transition>();
for (Transition t: transitions){
ts.add(t);
}
state.setTransitions(ts);
return state;
}
publicstatic Transition transition(Event event,String nextState){
Transition t=new Transition ();
t.setEvent(event);
return t;
}
publicstatic Event event(String event){
returnnew Event(event);
}
publicstatic String nextState(String nextState){
return nextState;
}
}
实现要点
由源码可以看出嵌套方法的实现比方法链要简单得多。
1 方法及返回值
由于无需维护对象状态,所以方法均为静态,返回值则直接是方法所要创建的模型对象。
2 方法及方法的参数来表明语义
通过Builder中的方法来定义语法中的词汇,方法的参数表明层次与包含的语义关系。
3 无需上下文变量
由于嵌套方法实现DSL巧妙的利用了系统中的方法调用栈,所以无需采用上下文变量来进行上下文的维护。
蔡超
HP 软件架构师
软件架构顾问
SCEA
IBM Certified Solution Designer for OOA&D vUML2
Chaocai2001@yahoo.com.cn