Open bxb100 opened 1 year ago
DSL:
Calc::= Left (Op Right)*
Left::= '(' int | Calc ')'
Right::= '(' int | Calc ')'
Op::= (add | sub | mul | div)
public interface Start {
Operation left(int left);
Operation left(End left);
}
public interface Operation {
Intermediate add();
Intermediate sub();
Intermediate mul();
Intermediate div();
}
public interface Intermediate {
End right(int right);
End right(End right);
}
public interface End extends Operation {
int end();
}
public class Calc {
public static void main(String[] args) {
Start start = new StartImpl();
System.out.println(start.left(1).add().right(2).end());
System.out.println(start.left(
start.left(1).mul().right(2)
).add().right(3).end());
System.out.println(start.left(1).add().right(
start.left(2).mul().right(3)
).end());
System.out.println(start.left(1).add().right(2).mul().right(1).end());
}
}
不足的地方:
(a+b)*c
没实现 a+b*c
只能有 a+(b*c)
public class StartImpl implements Start {
@Override
public Operation left(int left) {
return new OperationImpl(left);
}
@Override
public Operation left(End left) {
return new OperationImpl(left.end());
}
}
public class OperationImpl implements Operation {
int left;
public OperationImpl(int left) {
this.left = left;
}
public OperationImpl() {
}
@Override
public Intermediate add() {
return new IntermediateImpl(this, OperatorType.ADD);
}
@Override
public Intermediate sub() {
return new IntermediateImpl(this, OperatorType.SUB);
}
@Override
public Intermediate mul() {
return new IntermediateImpl(this, OperatorType.MUL);
}
@Override
public Intermediate div() {
return new IntermediateImpl(this, OperatorType.DIV);
}
}
public enum OperatorType {
ADD, SUB, MUL, DIV
}
public class IntermediateImpl implements Intermediate {
OperatorType type;
OperationImpl operation;
public IntermediateImpl(OperationImpl operation, OperatorType type) {
this.operation = operation;
this.type = type;
}
@Override
public End right(int right) {
return new EndImpl(this, right);
}
@Override
public End right(End right) {
return new EndImpl(this, right.end());
}
}
public class EndImpl extends OperationImpl implements End {
private final int right;
private final int left;
private final OperatorType op;
public EndImpl(IntermediateImpl intermediate, int right) {
if (intermediate.operation instanceof End) {
this.left = ((End) intermediate.operation).end();
} else {
this.left = intermediate.operation.left;
}
this.right = right;
this.op = intermediate.type;
}
@Override
public int end() {
return switch (this.op) {
case ADD -> this.left + this.right;
case SUB -> this.left - this.right;
case MUL -> this.left * this.right;
case DIV -> this.left / this.right;
};
}
}
自从 Martin Fowler 谈论流畅的接口以来,人们开始到处链式方法,为每个可能的用例创建流畅的 API(或 DSLs)。原则上,几乎所有类型的 DSL 都可以映射到 Java。让我们看看如何做到这一点
DSL 规则
DSL(领域特定语言)通常是根据大致如下的规则构建的
或者,你也可以像这样声明语法(由 Railroad Diagrams 站点 支持)
换句话说,你有一个开始条件或状态,在达到结束条件或状态之前,你可以从中选择一些你的语言的单词。它就像一个状态机,因此可以画成这样的图:
Java 实现这些规则
使用 Java 接口,对上述 DSL 进行建模非常简单。本质上,你必须遵循以下转换规则:
请注意,也可以使用类而不是接口对上述 DSL 进行建模。但是一旦你想重用相似的关键字,方法的多重继承可能会派上用场,你可能最好使用接口。
设置好这些规则后,您可以随意重复它们以创建任意复杂度的 DSL,例如 jOOQ。当然,您必须以某种方式实现所有接口,但那是另一回事了。
以下是将上述规则转换为 Java 的方式:
定义了上述语法后,我们现在可以直接在 Java 中使用此 DSL。以下是所有可能的结构:
最棒的是,您的 DSL 直接用 Java 编译!你得到一个免费的解析器。您还可以在 Scala(或 Groovy)中使用相同的表示法或在 Scala 中使用略有不同的表示法(省略点
.
和括号()
)重新使用此 DSL:实例
在 jOOQ 文档和代码库中可以看到一些真实世界的例子。这是从以前的一篇文章中摘录的使用 jOOQ 创建的相当复杂的 SQL 查询:
这有另一个例子,看起来对我很有吸引力。它称为 jRTF,用于以 fluent 的风格在 Java 中创建 RTF 文档:
总结
在过去的 7 年里,Fluent API 一直是一种炒作。 Martin Fowler 已经成为一个被大量引用的人并获得了大部分的荣誉,即使以前有 fluent APIs。在
java.lang.StringBuffer
中可以看到 Java 最古老的 fluent API 之一,它允许将任意对象附加到字符串。但是 fluent API 的最大好处是它能够轻松地将 external DSL 映射到 Java 并将它们实现为任意复杂度的 internal DSL 。