[java] 継承とジェネリックのFluent API



1 Answers

私は前にこのようなことをしてきました。 それは醜いことができます。 実際、私はそれを使ったよりも何度も試してみました。 通常は消去され、より良いデザインを見つけようとします。 それはあなたが道を少し下に移動するのを助けるために、これを試してみてください:

抽象クラスで以下のようなメソッドを宣言してください:

protected abstract T self();

これはあなたのリターンステートメントでこれを置き換えることができます。 サブクラスはTの境界に一致するものを返す必要がありますが、同じオブジェクトを返すことを保証するものではありません。

Question

私は、一連の "メッセージ"オブジェクトを設定してインスタンス化するための流暢なAPIを作成しています。 私はメッセージタイプの階層を持っています。

Fluent APIを使用するときにサブクラスのメソッドにアクセスできるようにするために、ジェネリックスを使用してサブクラスをパラメータ化し、すべての流暢メソッド( "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が安全でないreturn (T) this警告します。

型安全性:メッセージからTへのチェックされていないキャスト

このコードを本当に安全にするために、どのように階層を再編成できるかわかりません。 それが機能していても、このようなジェネリックの使用は本当に複雑です。 特に、警告を無視すれば実行時の例外が発生する状況を予測することはできません。 新しいメッセージタイプがあるので、コードを拡張可能にしておく必要があります。 ソリューションが継承を完全に回避することであれば、代替案の提案も得たいと思います。

同様の問題に取り組んでいるので、ここにはother questionsがあります。 彼らは、すべての中間クラスが抽象クラスであり、 protected abstract self()ようなメソッドを宣言するソリューションを指しています。 それでも、それは安全ではありません。




これは、元の問題の解決策ではありません。 あなたの実際の意思を捉え、元の問題がどこに現れないかというアプローチをスケッチするだけです。 (私はジェネリックが好きですが、 CommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>>ようなクラス名はCommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>> make me CommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>> ...)

私はこれが元々尋ねたものとは構造的にはかなり異なっていることを知っています。 可能な回答の範囲を絞り込んで、次の項目はもはや適用されないように質問でいくつかの詳細を省略したかもしれません。

しかし私があなたの意図を正しく理解していれば 、流暢な呼び出しによってサブタイプを処理させることを検討することができます。

ここでのアイデアは、最初は単純なMessage だけを作成できるということです。

Message m0 = Message.newMessage();
Message m1 = m0.withID("id");

このメッセージでは、 withIDメソッドを呼び出すことができます。これは、すべてのメッセージに共通する唯一のメソッドです。 この場合、 withIDメソッドはMessageを返します。

これまでは、メッセージはCommandMessageでも他の特殊な形式でもありません。 しかし、 withCommandメソッドを呼び出すと、明らかにCommandMessageを作成したいので、 CommandMessage返すだけCommandMessage

CommandMessage m2 = m1.withCommand("command");

同様に、 withParameterメソッドを呼び出すと、 withParameterます。

CommandWithParamsMessage m3 = m2.withParameter("name", "value");

このアイデアは、ドイツ語で書かれたブログエントリに触発されていますが、このコンセプトがどのようにタイプセーフな「Select-From-Where」クエリを構築するために使用されるかをうまく示しています。

ここでは、そのアプローチがスケッチされ、大まかにあなたのユースケースに適合します。 もちろん、実装が実際にどのように使用されるのかに依存する詳細がいくつかありますが、そのアイデアが明確になることを願っています。

import java.util.HashMap;
import java.util.Map;


public class FluentTest
{
    public static void main(String[] args) 
    {
        CommandWithParamsMessage msg = Message.newMessage().
                withID("do").
                withCommand("doAction").
                withParameter("arg", "value");


        Message m0 = Message.newMessage();
        Message m1 = m0.withID("id");
        CommandMessage m2 = m1.withCommand("command");
        CommandWithParamsMessage m3 = m2.withParameter("name", "value");
        CommandWithParamsMessage m4 = m3.withCommand("otherCommand");
        CommandWithParamsMessage m5 = m4.withID("otherID");
    }
}

class Message 
{
    protected String id;
    protected Map<String, String> contents;

    static Message newMessage()
    {
        return new Message();
    }

    private Message() 
    {
        contents = new HashMap<>();
    }

    protected Message(Map<String, String> contents) 
    {
        this.contents = contents;
    }

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

    public CommandMessage withCommand(String command) 
    {
        Map<String, String> newContents = new HashMap<String, String>(contents);
        newContents.put("command", command);
        return new CommandMessage(newContents);
    }

}

class CommandMessage extends Message 
{
    protected CommandMessage(Map<String, String> contents) 
    {
        super(contents);
    }

    @Override
    public CommandMessage withID(String id) 
    {
        this.id = id;
        return this;
    }

    public CommandWithParamsMessage withParameter(String paramName, String paramValue) 
    {
        Map<String, String> newContents = new HashMap<String, String>(contents);
        newContents.put(paramName, paramValue);
        return new CommandWithParamsMessage(newContents);
    }

}

class CommandWithParamsMessage extends CommandMessage 
{
    protected CommandWithParamsMessage(Map<String, String> contents) 
    {
        super(contents);
    }

    @Override
    public CommandWithParamsMessage withID(String id) 
    {
        this.id = id;
        return this;
    }

    @Override
    public CommandWithParamsMessage withCommand(String command) 
    {
        this.contents.put("command", command);
        return this;
    }
}





Related