除了方法链接外,静态工厂方法(static factory method)和import对于创建简洁易读的DSL来说是不错的助手。在下面的章节中,我们将更详细地讲到这些技术。
1、方法链接(Method Chaining)
使用方法链接来创建DSL有两种方式,这两种方式都涉及到链接中方法的返回值。我们的选择是返回this或者返回一个中间对象,这决定于我们试图要所达到的目的。
1.1、返回this
在可以以下列方式来调用链接中方法的时候,我们通常返回this:
◆可选择的。
◆以任何次序调用。
◆可以调用任何次数。
我们发现运用这个方法的两个用例:
1、相关对象行为链接。
2、一个对象的简单构造/配置。
1.1.1、相关对象行为链接
很多次,我们只在企图减少代码中不必要的文本时,才通过模拟分派“多信息”(或多方法调用)给同一个对象而将对象的方法进行链接。下面的代码段显示的是一个用来测试Swing GUI的API。测试所证实的是,如果一个用户试图不输入她的密码而登录到系统中的话,系统将显示一条错误提示信息。
| DialogFixture dialog = new DialogFixture(new LoginDialog()); dialog.show(); dialog.maximize(); TextComponentFixture usernameTextBox = dialog.textBox("username"); usernameTextBox.clear(); usernameTextBox.enter("leia.organa"); dialog.comboBox("role").select("REBEL"); OptionPaneFixture errorDialog = dialog.optionPane(); errorDialog.requireError(); errorDialog.requireMessage("Enter your password"); |
尽管代码很容易读懂,但却很冗长,需要很多键入。
下面列出的是在我们范例中所使用的TextComponentFixture的两个方法:
| public void clear() { target.setText(""); } public void enterText(String text) { robot.enterText(target, text); } |
我们可以仅仅通过返回this来简化我们的测试API,从而激活方法链接:
| public TextComponentFixture clear() { target.setText(""); return this; } public TextComponentFixture enterText(String text) { robot.enterText(target, text); return this; } |
在激活所有测试设施中的方法链接之后,我们的测试代码现在缩减到:
| DialogFixture dialog = new DialogFixture(new LoginDialog()); dialog.show().maximize(); dialog.textBox("username").clear().enter("leia.organa"); dialog.comboBox("role").select("REBEL"); dialog.optionPane().requireError().requireMessage("Enter your password"); |
这个结果代码显然更加简洁易读。正如先前所提到的,方法链接本身并不意味着有了DSL。我们需要将解决领域特定问题的对象的所有相关行为相对应的方法链接起来。在我们的范例中,这个领域特定问题就是Swing GUI测试。
1.1.2、对象的简单构造/配置
这个案例和上文的很相似,不同是,我们不再只将一个对象的相关方法链接起来,取而代之的是,我们会通过连贯接口创建一个“builder”来构建和/或配置对象。
下面这个例子采用了setter来创建“dream car”:
| DreamCar car = new DreamCar(); car.setColor(RED); car.setFuelEfficient(true); car.setBrand("Tesla"); |
DreamCar类的代码相当简单:
| // package declaration and imports public class DreamCar { private Color color; private String brand; private boolean leatherSeats; private boolean fuelEfficient; private int passengerCount = 2; // getters and setters for each field } |

