3

メソッドが流暢で、任意の順序で呼び出すことができる拡張可能なクラス階層をJavaで作成できますか? (はい! 以下の回答を参照してください)、メソッドが流暢であれば、ソースにアクセスできない既存のクラスであっても!

私は既存の階層を改造しており、ファクトリまたは少なくともジェネリック コンストラクターと (最終的には) 不変のビルダー パターン(JB P.14) を使用したいと考えています。 フィールドを設定するメソッドは戻ります- 代わりvoidにジェネリックを返す方が良いでしょう - そうすれば、Tメソッドチェーンを行う能力を得ることができます (それらはすべて今呼び出しsuperます)。

目標:
1. すべてのクラスで静的な getFactory() メソッドを作成する必要がないようにします。
2. シンプルなメソッド シグネチャ。
3. ジェネリックでありながら、コンパイル時に問題をキャッチするファクトリ メソッドを作成します。
4. エラーが発生した場合、実行時エラーではなくコンパイル時エラーを取得します。

要求どおり、非汎用コードは非常に単純ですが、機能しません。

public class A {
    private String a = null;
    protected A setA(String a){
        this.a = a;
        return this;//<== DESIRE THIS TO BE CHAINABLE
    }
    protected static A factory(){
       return new A();
    }
}  

.

public class B extends A {
    private String b = null;
    protected Foo setB(String b){
        this.b = b;
        return this;//<== DESIRE THIS TO BE CHAINABLE
    }
    protected static B factory(){
        return new B();
    }
}

これで、発信者は電話を試みることができますB.factory().setA("a").setB("b")//won't compile

しかし、ではなく をsetA()返すため、コンパイルできません。の代わりにinをオーバーライドし、呼び出して返すことで機能させることができます。これらの各メソッドの委任を避けることがポイントです。単純に、任意の順序で呼び出すことができる連鎖可能なクラス メソッドの拡張可能なグループが必要です。明らかに動作します。ABsetA()BsetB()BAB.getFactory().B("b").A("a")

4

4 に答える 4

3

答えは (私の驚きと満足に) YES です。私は自分でこの質問に答えました: メソッド呼び出しが問題のクラスのインスタンスを返す場合、これは少しの作業で実行できます (以下の chainable を参照)。トップレベルのソースを編集できる場合、これを行うさらに簡単な方法も見つけました。

最上位クラス (A) では:

protected final <T> T a(T type) {
    return type
}

C が B を拡張し、B が A を拡張するとします。

呼び出し:

C c = new C();
//Any order is fine and you have compile time safety and IDE assistance.
c.setA("a").a(c).setB("b").a(c).setC("c");

例 1 と 3 は、既存のクラス階層を流暢にする方法であり、既存のクラスが流暢である限り、メソッドを任意の順序で呼び出せるようにする方法です (ただし、ソースにアクセスできないか、ソースを変更することはできません)。WAY2 は、ソースにアクセスでき、呼び出しをできるだけ単純にしたい例です。

完全な SSCCE:

import static java.lang.System.out;

public class AATester {
    public static void main(String[] args){

        //Test 1:
        for(int x: new int[]{ 0, 1, 2 } ){
            A w = getA(x);
            //I agree this is a nasty way to do it... but you CAN do it.
            Chain.a(w.setA("a1")).a(w instanceof C ? ((C) w).setC("c1") : null );
            out.println(w);
        }

        //Test for WAY 2: Hope this wins Paul Bellora's approval 
        //for conciseness, ease of use and syntactic sugar.
        C c = new C();
        //Invoke methods in any order with compile time type safety!
        c.setA("a2").a(c).setB("b2").a(c).set("C2");
        out.println(w);

        //Example 3, which is Example 1, but where the top level class IS known to be a "C"
        //but you don't have access to the source and can't add the "a" method to the 
        //top level class.  The method invocations don't have to be as nasty as Example 1.
        c = new C();
        Chain.a(c.setA("a3")).a(c.setB("b3")).a(c.setC("c3"));//Not much larger than Example 2.
        out.println(w);
    }
    public static getA(int a){//A factory method.
        A retval;//I don't like multiple returns.
        switch(a){
            case 0:  retval = new A(); break;
            case 1:  retval = new B(); break;
            default: retval = new C(); break;
        }
        return retval;
    }
}

テスト クラス A

public class A {
   private String a;
   protected String getA() { return a; }

   //WAY 2 - where you have access to the top level source class.
   protected final <T> T a(T type) { return type; }//This is awesome!       

   protected A setA(String a) { this.a=a; return this; }//Fluent method
   @Override
   public String toString() {
      return "A[getA()=" + getA() + "]";
   }
}

テスト クラス B

public class B extends A {
   private String b;
   protected String getB() { return b; }
   protected B setB(String b) { this.b=b; return this; }//Fluent method
   @Override
   public String toString() {
      return "B[getA()=" + getA() + ", getB()=" + getB() + "]\n  " 
      + super.toString();
  }
}

テスト クラス C

public class C extends B {
   private String c;
   protected String getC() { return c; }
   protected C setC(String c) { this.c=c; return this; }//Fluent method
   @Override
   public String toString() {
      return "C [getA()=" + getA() + ", getB()=" + getB() + ", getC()=" 
             + getC() + "]\n  " + super.toString();
   }
}

チェーンクラス

/**
 * Allows chaining with any class, even one you didn't write and don't have 
 * access to the source code for, so long as that class is fluent.
 * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved. 
 */
public final class Chain {
   public static <K> _<K> a(K value) {//Note that this is static
      return new _<K>(value);//So the IDE names aren't nasty
   }
}

チェーンのヘルパー クラス。

/** 
 * An instance method cannot override the static method from Chain, 
 * which is why this class exists (i.e. to suppress IDE warnings, 
 * and provide fluent usage). 
 *
 * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved.
 */
final class _<T> {
   public T a;//So we may get a return value from the final link in the chain.
   protected _(T t) { this.a = t }//Required by Chain above
   public <K> _<K> a(K value) {
      return new _<K>(value);
   }
}

出力:

A [get(A)=a]
B [get(A)=a, getB()=null]
  A [getA()=a]
C [getA()=a, getB()=null, getC()=c)]
  B [get(A)=a, getB()=null]
  A [get(A)=a]

QED。:)

これを行う人を見たことがありません。これは新しい、潜在的に価値のある技術になると思います。

PS 「elvis様の使い方」に関しては、1本か2本 vs 8本以上です。

本 b = null;
パブリッシャー p = null;
書籍のリスト = null;
String id = "メルニボーンのエルリック";

books = Chain.a(b = findBook(id)).a(b != null ? p = b.getPublisher() : null)
                                 .a(p != null ? p.getPublishedBooks(): null).a;

out.println(books==null ? null : Arrays.toString(books.toArray()));

対:

本 b = null;
パブリッシャー p = null;
書籍のリスト = null;
String id = "メルニボーンのエルリック";

b = findBook(id);
配列 [] 本 = null;
if( b != null ) {
    p = b.getPublisher();
    if(p != null) {
        本 = p.getPublishedBooks();
    }
}

out.println(books==null ? null : Arrays.toString(books.toArray()));

NPE はありません。チェーンが完了すると、"Elric of Melnibone" の出版社から出版されたすべての本 (つまり、"Ace" 出版社が出版したすべての本) を取得し、そうでない場合は null を取得します。

于 2013-11-05T23:01:59.970 に答える
0

ソース コードにアクセスできる場合は、Alan が書いたものを拡張することによって、継承と非常にコンパクトな構文を可能にしながらジェネリックを非表示にする補足クラスを追加します。BaseA と BaseB は階層を実行し、A と B はジェネリックを非表示にします。

BaseA
 +- A
 +- BaseB
     +- B


public class BaseA<S extends BaseA<?>> {
    private String a = null;

    protected BaseA() {
    }

    @SuppressWarnings("unchecked")
    public S setA(String a) {
        this.a = a;
        return (S) this;
    }

}

public class A extends BaseA<A> {
    public static A factoryA() {
        return new A();
    }
}

public class BaseB<S extends BaseB<?>> extends BaseA<S> {
    private String b = null;

    protected BaseB() {
    }

    @SuppressWarnings("unchecked")
    public S setB(String b) {
        this.b = b;
        return (S) this;
    }

}

public class B extends BaseB<B> {
    public static B factoryB() {
        return new B();
    }
}

public class Main {
    public static void main(String[] args) {
        B.factoryB().setA("").setB("").setB("").setA("").setA("");
    }
}
于 2013-11-08T17:10:48.040 に答える
-1

流暢なインターフェイスは、既に持っているコマンド クエリ メソッドの通常のセットとは異なる問題です。関心の分離は、それらを分離することをお勧めします。

コードの既存の階層があるため、汚れた作業を行う流暢なファサードを作成します。

Martin Fowler: Domain-Specific Languages、4.2: The need for a Parsing Layer も参照してください。

于 2013-11-07T00:57:21.023 に答える