具有继承和泛型的流畅API

Boj*_*Boj 31 java generics fluent-interface

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

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

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

    protected Message() {

    }

    public T withID(String id) {
        return (T) this;
    }
}
Run Code Online (Sandbox Code Playgroud)

具体子类同样重新定义泛型类型.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

CommandWithParamsMessage msg = CommandWithParamsMessage.newMessage()
        .withID("do")
        .withCommand("doAction")
        .withParameter("arg", "value");
Run Code Online (Sandbox Code Playgroud)

以任何顺序调用流畅的方法是这里的主要目标.

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

类型安全:从Message到T取消选中

我不确定如何重新组织层次结构以使此代码真正安全.尽管它有效,但以这种方式使用仿制药确实令人费解.特别是,如果我忽略警告,我无法预见会发生运行时异常的情况.将有新的消息类型,所以我需要保持代码可扩展.如果解决方案是完全避免继承,我也想获得替代方案的建议.

此处还有其他 问题可解决类似问题.他们指出一个解决方案,其中所有中间类都是抽象的,并声明一个类似的方法protected abstract self().最后,它还不安全.

phi*_*686 19

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

此外,它也没有必要在这里使用泛型.普通的旧继承会很好.由于子类型可以通过使返回类型更具体来覆盖方法,因此您可以:

public abstract class Message {

    protected Message() {

    }

    public Message withID(String id) {
        return this;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后

public class CommandMessage extends Message {

    protected CommandMessage() {
        super();
    }

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

    public CommandMessage withCommand(String command) {
        return this;
    }
}
Run Code Online (Sandbox Code Playgroud)

这可以正常工作,因为您可以按正确的顺序调用参数:

CommandWithParamsMessage.newMessage()
    .withID("do")
    .withCommand("doAction")
    .withParameter("arg", "value");
Run Code Online (Sandbox Code Playgroud)

会失败,但是

CommandWithParamsMessage.newMessage().withParameter("arg", "value")
.withCommand("doAction").withID("do")
Run Code Online (Sandbox Code Playgroud)

将成功,因为它只"up类型",最后返回一个"消息"类.如果你不想"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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您将使用上面两个流畅的调用中的任何一个流畅地返回CommandWithParamsMessage.

这会解决您的问题,还是我误解了您的意图?

  • "子类型的+1可以通过使其返回类型更具体来覆盖方法" (3认同)

Wil*_*ice 14

我之前做过类似的事.它可能变得丑陋.事实上,我尝试过的次数比我用过的次数多; 通常它会被删除,我试图找到一个更好的设计.也就是说,为了帮助你在路上走得更远,试试这个:

让您的抽象类声明一个方法,如:

protected abstract T self();
Run Code Online (Sandbox Code Playgroud)

这可以this在您的return语句中替换.子类将需要返回与绑定匹配的内容T- 但不保证它们返回相同的对象.


Har*_*ezz 9

如果您更改这样的签名,您既不应该收到任何警告,也不需要任何演员:

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

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

    protected abstract T self();
}

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

    public T withCommand(String command) {
        // do some work ...
        return self();
    }
}

class CommandWithParamsMessage extends CommandMessage<CommandWithParamsMessage> {

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

    public CommandWithParamsMessage withParameter(String paramName, String paramValue) {
        // do some work ...
        return this;
    }

    @Override protected CommandWithParamsMessage self() {
        return this;
    }
}
Run Code Online (Sandbox Code Playgroud)