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