语言性(language nature):DSL 是一种程序设计语言,因此它必须具备连贯的表达能力——不管是一个表达式还是多个表达式组合在一起。
受限的表达性(limited expressiveness):通用程序设计语言提供广泛的能力:支持各种数据、控制,以及抽象结构。这些能力很有用,但也会让语言难于学习和使用。DSL 只支持特定领域所需要特性的最小集。使用 DSL,无法构建一个完整的系统,相反,却可以解决系统某一方面的问题。
针对领域(domain focus):只有在一个明确的小领域下,这种能力有限的语言才会有用。这个领域才使得这种语言值得使用。
/\d{3}-\d{3}-\d{4}/
Internal DSL 是一种通用语言的特定用法。用内部 DSL 写成的脚本是一段合法的程序,但是它具有特定的风格,而且只用到了语言的一部分特性,用于处理整个系统一个小方面的问题。用这种 DSL 写出的程序有一种自定义语言的风格,与其所使用的宿主语言有所区别。例如我们的状态机就是 Internal DSL,它不支持脚本配置,使用的时候还是 Java 语言,但并不妨碍它也是 DSL。
builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(checkCondition())
.perform(doAction());
External DSL 是一种“不同于应用系统主要使用语言”的语言。外部 DSL 通常采用自定义语法,不过选择其他语言的语法也很常见(XML 就是一个常见选 择)。比如像 Struts 和 Hibernate 这样的系统所使用的 XML 配置文件。
Workbench 是一个专用的 IDE,简单点说,工作台是 DSL 的产品化和可视化形态。
Internal DSL:假如你只是为了增加代码的可理解性,不需要做外部配置,我建议使用 Internal DSL,简单、方便、直观。
External DSL:如果你需要在 Runtime 的时候进行配置,或者配置完,不想重新部署代码,可以考虑这种方式。比如,你有一个规则引擎,希望增加一条规则的时候,不需要重复发布代码,那么可以考虑 External。
Workbench:配置也好,DSL Script 也好,这东西对用户不够友好。比如在淘宝,各种针对商品的活动和管控规则非常复杂,变化也快。我们需要一个给运营提供一个 workbench,让他们自己设置各种规则,并及时生效。这时的 workbench 将会非常有用。
在现代生活中,简单的做法一直难以实现,因为它有违某些努力寻求复杂化以证明其工作合理性的人所秉持的精神。
when(mockedList.get(anyInt())).thenReturn("element")
when(mockedList.get(anyInt())).thenReturn("element")
String element = mockedList.get(anyInt());
boolean isExpected = "element".equals(element);
OkHttpClient.Builder builder=new OkHttpClient.Builder();
OkHttpClient okHttpClient=builder
.readTimeout(5*1000, TimeUnit.SECONDS)
.writeTimeout(5*1000, TimeUnit.SECONDS)
.connectTimeout(5*1000, TimeUnit.SECONDS)
首先,状态机的实现可以非常的轻量,最简单的状态机用一个 Enum 就能实现,基本是零成本。
其次,使用状态机的 DSL 来表达状态的流转,语义会更加清晰,会增强代码的可读性和可维护性。
简洁的仅支持状态流转的状态机,不需要支持嵌套、并行等高级玩法。
状态机本身需要是 Stateless(无状态)的,这样一个 Singleton Instance 就能服务所有的状态流转请求了。
状态机领域模型
State:状态
Event:事件,状态由事件触发,引起变化
Transition:流转,表示从一个状态到另一个状态
External Transition:外部流转,两个不同状态之间的流转
Internal Transition:内部流转,同一个状态之间的流转
Condition:条件,表示是否允许到达某个状态
Action:动作,到达某个状态之后,可以做什么
StateMachine:状态机
//StateMachine
public class StateMachineImpl<S,E,C> implements StateMachine<S, E, C> {
private String machineId;
private final Map<S, State<S,E,C>> stateMap;
...
}
//State
public class StateImpl<S,E,C> implements State<S,E,C> {
protected final S stateId;
private Map<E, Transition<S, E,C>> transitions = new HashMap<>();
...
}
//Transition
public class TransitionImpl<S,E,C> implements Transition<S,E,C> {
private State<S, E, C> source;
private State<S, E, C> target;
private E event;
private Condition<C> condition;
private Action<S,E,C> action;
...
}
状态机的 Fluent API
class TransitionBuilderImpl<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C>, From<S,E,C>, On<S,E,C>, To<S,E,C> {
...
@Override
public From<S, E, C> from(S stateId) {
source = StateHelper.getState(stateMap,stateId);
return this;
}
@Override
public To<S, E, C> to(S stateId) {
target = StateHelper.getState(stateMap, stateId);
return this;
}
...
}
状态机的无状态设计
StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
//external transition
builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(checkCondition())
.perform(doAction());
//internal transition
builder.internalTransition()
.within(States.STATE2)
.on(Events.INTERNAL_EVENT)
.when(checkCondition())
.perform(doAction());
//external transitions
builder.externalTransitions()
.fromAmong(States.STATE1, States.STATE2, States.STATE3)
.to(States.STATE4)
.on(Events.EVENT4)
.when(checkCondition())
.perform(doAction());
builder.build(machineId);
亲爱的同学,看完本文你对 DSL 有什么新的认识呢?写 DSL 的时候遇到过哪些坑?对于 DSL 你还有什么疑惑的地方吗?
欢迎同学们在评论区留言,作者张建飞将选出 5 个评论回复,并送出他的新书《代码精进之路》,这是一本专门为程序员写的书,帮助广大程序员培养良好的编程习惯和思维。
阿里巴巴 2020 实习生招聘空中宣讲会来了!6 位阿里大咖助你拿 offer,识别下方二维码或点击文末“阅读原文”即可收看回放: