流利的API与继承和泛型 [英] Fluent API with inheritance and generics

查看:112
本文介绍了流利的API与继承和泛型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

  public abstract class Message< T extends Message< T>> {
$ b $ protected Message(){

}

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






$ b

具体的子类重新定义泛型类型。 p>

  public class CommandMessage< T extends CommandMessage< T>>扩展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);
返回此;




$ b $ p
$ b

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

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

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



但是,编译器警告所有 return(T)this 是不安全的。
$ b


类型安全性:未经检查而从消息转换为T


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



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

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



此外,在此处也不必使用泛型。简单的旧的继承将正常工作。由于子类型可以通过使返回类型更具体来覆盖方法,因此可以有:

  public abstract class Message {

protected Message(){

}

public带ID(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;




$ b $ p
$ b

这样可以很好地工作,您的参数以正确的顺序排列:

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

会失败,但

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

会成功,因为它只是up类型,最后返回一个消息类。如果你不希望它uptype,那么简单地覆盖继承的命令,现在你可以以任何顺序调用这些方法,因为它们都返回原始类型。



EG

  public class CommandWithParamsMessage extends 
CommandMessage {

public static CommandWithParamsMessage newMessage(){
返回新的CommandWithParamsMessage();
}

public CommandWithParamsMessage withParameter(String paramName,
String paramValue){
contents.put(paramName,paramValue);
返回此;


@Override
public CommandWithParamsMessage withCommand(String command){
super.withCommand(command);
返回此;
}

@Override
公共CommandWithParamsMessage ID(String s){
super.withID(s);
返回此;






$ b现在你将流利地返回一个CommandWithParamsMessage与上面有两个流利的电话。



这是否能解决您的问题,或者我误解了您的意图?


I'm writing a fluent API to configure and instantiate a series of "message" objects. I have a hierarchy of message types.

To be able to access method of subclasses when using the fluent API, I used generics to parametrize the subclasses and make all fluent methods (that start with "with") return the generic type. Note that I omitted most of the body of the fluent method; a lot of configuration goes on in them.

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

    protected Message() {

    }

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

The concrete subclasses redefine the generic type similarly.

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;
    }
}

This code works, i.e. I can instantiate any of the classes and use all fluent methods:

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

Calling the fluent methods in any order is a major goal here.

However, the compiler warns that all return (T) this are unsafe.

Type safety: Unchecked cast from Message to T

I'm unsure how I could reorganize the hierarchy to make this code truly safe. Even though it works, the use of generics in this fashion feels really convoluted. Especially, I'm not able to foresee situations where runtime exceptions will happen if I just ignore the warnings. There will be new message types, so I need to keep the code extensible. If the solution is to avoid inheritance altogether I would also like to obtain suggestion of alternatives.

There are other questions here on SO that address a similar issue. They point to a solution where all intermediate classes are abstract and declare a method like protected abstract self(). Still, in the end it's not safe.

解决方案

Your code is fundamentally an unsafe use of Generics. For example, if I write a new class which extends message, say Threat, and has a new method doSomething(), and then I create a message parameterised by this new class and it creates an instance of Message, and then attempts to Cast it to its subclass. However, since it is an instance of Message, and not of Threat, an attempt to call this message will cause an Exception. Since it is not possible for Message to doSOmething().

Further, its also unnecessary to use Generics here. Plain old inheritance will work fine. Since sub types can override methods by making their return types more specific, you can have:

public abstract class Message {

    protected Message() {

    }

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

And then

public class CommandMessage extends Message {

    protected CommandMessage() {
        super();
    }

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

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

This will work fine, on the understanding that you call your arguments in the right order:

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

will fail, but

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

Will succeed, since it only "up types", finally returning a "message" class. If you want it not to "uptype", then simply overwrite the inherited commands, and now you can call the methods in any order, since they are all return the original type.

E.g.

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;
    }
}

Now you will fluently return a CommandWithParamsMessage with either of the two fluent calls above.

Does this solve your problem, or have I misunderstood your intent?

这篇关于流利的API与继承和泛型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆