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()返回的是Aa ,而不是 a B。您可以通过覆盖setA()in B、调用setB()和返回B而不是A. 避免对这些方法中的每一个进行委派是重点。我只是想要一组可扩展的可链接类方法,它们可以按任何顺序调用。B.getFactory().B("b").A("a")显然有效。

4

4 回答 4

3

答案(令我惊讶和满意)是肯定的。我自己回答了这个问题:如果方法调用返回有问题的类的实例,你可以做一些工作(见下面的可链接)。如果您可以编辑顶级源,我还发现了一种更简单的方法:

顶级班(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
   }
}

Chain 的助手类。

/** 
 * 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关于“elvislike用法”,是1或2行vs 8行或更多。

书 b = null;
发布者 p = null;
列出书籍 = null;
String id = "Elric of Melnibone";

book = 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 = "Elric of Melnibone";

b = findBook(id);
数组 [] 书籍 = null;
如果(乙!=空){
    p = b.getPublisher();
    如果(p!= null){
        书籍 = p.getPublishedBooks();
    }
}

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

没有NPE,如果链条完成,您将获得“Elric of Melnibone”出版商出版的所有书籍(即“Ace”出版商出版的所有书籍),如果没有,您将获得空值。

于 2013-11-05T23:01:59.970 回答
3

我相信有一种方法可以使用泛型来做到这一点......语法比预期的要干净一些......

这是客户端代码...

    B<B> b = B.factoryB();
    b.setA("a").setB("b");

    A<A> ba = A.factoryA();

    ba.setA("a");

顶级(真实)类

public  class A<S extends A> extends Chained<S> {
    private String a = null;

    protected A() {
        }

    public S setA(String a) {
        this.a = a;
        return me();
    }
    public static A<A> factoryA() {
        return new A<A>();
    }
}

示例子类

public  class B<S extends B> extends A<S> {
    private String b = null;

    B() {
    }

    public S setB(String b) {
        this.b = b;
        return me();
    }
    public static B<B> factoryB() {
        return new B<B>();

    }

}

帮手

public  abstract class Chained<S extends Chained> {
    // class should be extended like:
    // ... class A<S extends A> extends Chained<S>

    public Chained() {
    }

    public final S me() {
        return (S) this;
    }
}

它远非完美,可以不工作(如果你真的想)

于 2013-11-07T17:11:10.340 回答
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:领域特定语言,4.2:解析层的需要。

于 2013-11-07T00:57:21.023 回答