具有继承和泛型的 Fluent API

2022-09-01 08:41:07

我正在编写一个流畅的API来配置和实例化一系列“消息”对象。我有一个消息类型的层次结构。

为了能够在使用 fluent API 时访问子类的方法,我使用泛型来参数化子类,并使所有 fluent 方法(以“with”开头)返回泛型类型。请注意,我省略了流利方法的大部分主体;它们中进行了很多配置。

public abstract class Message<T extends Message<T>> {

    protected Message() {

    }

    public T withID(String id) {
        return (T) this;
    }
}

具体子类以类似方式重新定义泛型类型。

public class CommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>> {

    protected CommandMessage() {
        super();
    }

    public static CommandMessage newMessage() {
        return new CommandMessage();
    }

    public T withCommand(String command) {
        return (T) this;
    }
}

public class CommandWithParamsMessage extends
    CommandMessage<CommandWithParamsMessage> {

    public static CommandWithParamsMessage newMessage() {
        return new CommandWithParamsMessage();
    }

    public CommandWithParamsMessage withParameter(String paramName,
        String paramValue) {
        contents.put(paramName, paramValue);
        return this;
    }
}

这个代码有效,即我可以实例化任何类并使用所有流畅的方法:

CommandWithParamsMessage msg = CommandWithParamsMessage.newMessage()
        .withID("do")
        .withCommand("doAction")
        .withParameter("arg", "value");

以任何顺序调用流利的方法都是这里的主要目标。

但是,编译器警告所有操作都不安全。return (T) this

类型安全:未经检查的从“消息”到“T”的强制转换

我不确定如何重新组织层次结构以使此代码真正安全。即使它有效,以这种方式使用泛型感觉真的很复杂。特别是,我无法预见如果我忽略警告,运行时异常将发生的情况。将会有新的消息类型,因此我需要保持代码可扩展性。如果解决方案是完全避免继承,我也想获得替代方案的建议。

SO上还有其他问题可以解决类似的问题。它们指向一个解决方案,其中所有中间类都是抽象的,并声明了一个类似 的方法。尽管如此,最终它并不安全。protected abstract self()


答案 1

您的代码从根本上说是对泛型的不安全使用。例如,如果我写了一个扩展消息的新类,比如说威胁,并且有一个新方法doSomething(),然后我创建了一个由这个新类参数化的消息,它创建了一个Message的实例,然后尝试将其强制转换为它的子类。但是,由于它是 Message 的实例,而不是威胁的实例,因此尝试调用此消息将导致异常。因为Message不可能doSOmething()。

此外,这里也没有必要使用泛型。普通的旧继承将正常工作。由于子类型可以通过使方法的返回类型更具体来重写方法,因此您可以:

public abstract class Message {

    protected Message() {

    }

    public Message withID(String id) {
        return this;
    }
}

然后

public class CommandMessage extends Message {

    protected CommandMessage() {
        super();
    }

    public static CommandMessage newMessage() {
        return new CommandMessage();
    }

    public CommandMessage withCommand(String command) {
        return this;
    }
}

这将正常工作,前提是您以正确的顺序调用参数:

CommandWithParamsMessage.newMessage()
    .withID("do")
    .withCommand("doAction")
    .withParameter("arg", "value");

将失败,但

CommandWithParamsMessage.newMessage().withParameter("arg", "value")
.withCommand("doAction").withID("do")

将成功,因为它只“up types”,最终返回一个“message”类。如果你不希望它“uptype”,那么只需覆盖继承的命令,现在你可以按任何顺序调用这些方法,因为它们都返回原始类型。

例如:

public class CommandWithParamsMessage extends
CommandMessage {

    public static CommandWithParamsMessage newMessage() {
        return new CommandWithParamsMessage();
    }

    public CommandWithParamsMessage withParameter(String paramName,
        String paramValue) {
        contents.put(paramName, paramValue);
        return this;
    }

    @Override
    public CommandWithParamsMessage withCommand(String command){
        super.withCommand(command);
        return this;
   }

    @Override
    public CommandWithParamsMessage withID(String s){
        super.withID(s);
        return this;
    }
}

现在,您将流利地返回CommandWithParamsMessage,其中包含上述两个流利调用中的任何一个。

这是否解决了你的问题,还是我误解了你的意图?


答案 2

我以前做过这样的事情。它可能会变得丑陋。事实上,我尝试过它的次数比我使用过的次数还多。通常它会被删除,我试图找到一个更好的设计。也就是说,为了帮助你走得更远,请尝试以下操作:

让你的抽象类声明一个类似这样的方法:

protected abstract T self();

这可以替换您的退货报表。子类将被要求返回与 bound 匹配的内容 -- 但它并不能保证它们返回相同的对象。thisT


推荐