如何在没有静态方法的情况下设计 Java 流畅的 DSL API

问题描述

定义 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);
        }
    }
}

但是如何调用 setComputer

解决方法

可以使用反射和匿名实例。

您只需要它用于驱动器

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"));

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...