引言
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