Welcome

首页 / 软件开发 / 数据结构与算法 / 演化架构和紧急设计 - 连贯接口

演化架构和紧急设计 - 连贯接口2010-10-28 IBM Neal Ford本 系列 的 上一期 介绍了如何使用特定领域语言(DSL)来捕获域惯用模式。本期将继续该主题,展示各种 DSL 构建方法。

在下一本书 Domain Specific Languages 中,Martin Fowler 将阐述两种 DSLs 之间的区别。外部 DSLs 可构建一个新语法,构建时需要使用 lexx 和 yacc 或 Antlr 等工具。一个内部 DSL 在基本语言基础之上构建新的语言,并借用和样式化基本语言的语法。本期示例将以 Java™ 为基本语言构建内部 DSLs,在其语法之上构建新的小型语言。

强调通过以下所有方法构建 DSLs 是隐式上下文 的概念所在。DSLs(特别是内部 DSLs)试图通过创建围绕相关元素的上下文包装器来消除繁杂的语法。该概念的一个典型示例是 XML 中的父元素和子元素,这些元素提供一个围绕相关项目的包装器。您会注意到,这些 DSL 方法中有许多使用语言语法技巧来达到同样的效果。

可读性是使用 DSL 的优势之一。如果您编写非开发人员可以读懂的代码,那么就缩短了您的团队与请求相关代码功能的人之间的反馈环路。Fowler 的书中确定的一个公共 DSL 模式叫作连贯接口,他将其定义为能够为一系列方法调用中转或维护指令上下文的行为。我会向您展示几种连贯接口,首先是方法链接。

方法链接

方法链接使用方法的返回值来中转指令上下文,在这种情况下是进行第一个方法调用的对象实例。这听起来要复杂多了,因此我要通过一个示例来阐明该概念。

使用 DSLs 时,常常需要从目标语法开始向后运转,以弄清如何实现它。从终点开始行得通是因为可读性在 DSLs 中非常重要。我要使用的示例是一个跟踪日历条目的小型应用程序。该应用程序阐释了 DSL 的语法,如清单 1 所示:

清单 1. 日历 DSL 的目标语法

public class CalendarDemoChained {

public static void main(String[] args) {
new CalendarDemoChained();
}

public CalendarDemoChained() {
Calendar fourPM = Calendar.getInstance();
fourPM.set(Calendar.HOUR_OF_DAY, 16);
Calendar fivePM = Calendar.getInstance();
fivePM.set(Calendar.HOUR_OF_DAY, 17);

AppointmentCalendarChained calendar =
new AppointmentCalendarChained();
calendar.add("dentist").
from(fourPM).
to(fivePM).
at("123 main street");

calendar.add("birthday party").at(fourPM);
displayAppointments(calendar);
}

private void displayAppointments(AppointmentCalendarChained calendar) {
for (Appointment a : calendar.getAppointments())
System.out.println(a.toString());
}
}

在上面处理完 Java 日历必要的繁琐设置之后,您可以看到,在我向两个日历条目添加值之后,方法链接连贯接口开始运作。注意,我使用空格来分隔那部分单一代码行(从 Java 语法角度来看)。样式化基本语言以使 DSL 更加可读的行为在内部 DSLs 中是很常见的。

Appointment 类包含清单 2 中出现的大部分连贯接口方法:

清单 2. Appointment 类

public class Appointment {
private String _name;
private String _location;
private Calendar _startTime;
private Calendar _endTime;

public Appointment(String name) {
this._name = name;
}

public Appointment() {
}

public Appointment name(String name) {
_name = name;
return this;
}
public Appointment at(String location) {
_location = location;
return this;
}

public Appointment at(Calendar startTime) {
_startTime = startTime;
return this;
}

public Appointment from(Calendar startTime) {
_startTime = startTime;
return this;
}

public Appointment to(Calendar endTime) {
_endTime = endTime;
return this;
}

public String toString() {
return "Appointment:"+ _name +
((_location != null && _location.length() > 0) ?
", location:" + _location : "") +
", Start time:" + _startTime.get(Calendar.HOUR_OF_DAY) +
(_endTime != null? ", End time: " +
_endTime.get(Calendar.HOUR_OF_DAY) : "");
}
}