问题描述
定义 Java fluent API 的方法有多种:
- 使用静态类/方法 + 内部
ThreadLocal
来获取 DSL 实例 - 使用基于 setter/getter 的方法链接 API。我个人不喜欢它的可读性,例如查看一些 Spring Security 代码示例。
有没有办法创建一个更具可读性的 Java DSL,比如 Kotlin 中的流畅方法?例如,具有 Computer
/cpu
等属性的 gpu
类和驱动器等嵌套 DSL 类(注意:我想进一步嵌套 ssd 和 hdd,例如定义partitions
?
public class Main {
public static void main(String[] arguments) {
Computer computer = Computer.build("Gaming PC").cpu("AMD 5950X").gpu("Lol rly?");
computer.drives(() {
ssd("1TB","m2");
hdd("10TB","SATA");
});
computer.print();
}
public static class Computer {
private final Map<String,String> attributes;
private Computer() {
this.attributes = new LinkedHashMap<>();
}
public static Computer build(String name) {
Computer computer = new Computer();
computer.attributes.put("name",name);
return computer;
}
public Computer cpu(String modelName) {
attributes.put("cpu",modelName);
return this;
}
public Computer gpu(String modelName) {
attributes.put("gpu",modelName);
return this;
}
// Todo: Add ssd and hdd. How?
public void print() {
for (Map.Entry<String,String> entry : attributes.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
}
上面的例子无法编译。但是有没有办法用常规类/抽象类甚至接口 [使用默认方法?] 来实现它。像这样:
public class Main {
public static void main(String[] arguments) {
Computer computer = Computer.build("Gaming PC").cpu("AMD 5950X").gpu("Lol rly?");
computer.drives((new DriveManager() {
ssd("1TB","SATA");
});
computer.print();
}
public static class DriveManager {
private Computer computer;
public void setComputer(Computer computer) {
this.computer = computer;
}
public void ssd(String size,String interfaceType) {
computer.ssd(size,interfaceType);
}
public void hdd(String size,interfaceType);
}
}
}
解决方法
可以使用反射和匿名实例。
您只需要它用于驱动器
Computer computer = new Computer("Gaming PC") {
MainboardComponent cpu = new CPU("AMD 5950X");
MainboardComponent gpu = new GPU("Lol rly?");
Drive ssd = new SSD("1TB","m2");
Drive hdd = new HDD("10TB","SATA") {
Partition hdd1 = ...;
Partition hdd2 = ...;
};
}
但是这会隐藏这些字段。
然而,这对于您可以在多个级别(受保护的字段、预定义的规则、I/O 端口)上使用继承的语法之类的东西很有用。优点是容器基类可以提供组件类和受保护的成员。
通过让 ComputerBuilder 有一个嵌套的 DriveBuilder,一个纯流畅的界面也是可能的。
Computer computer = Computer.build("Gaming PC")
.cpu("AMD 5950X")
.gpu("Lol rly?")
.drives()
.ssd("1TB","m2")
.hdd("10TB","SATA")
.endDrives()
.end();
第三种选择是使用可变参数方法 ComputerBuilder drives(DriveBuilder... dds);
(Drive 或 DriveBuilder)。
Computer computer = Computer.build("Gaming PC")
.cpu("AMD 5950X")
.gpu("Lol rly?")
.drives(Drive.ssd("1TB","m2"),Drive.hdd("10TB","SATA"));