在java中,可以创建一个流畅的可扩展类层次结构,其中的方法可以按任何顺序调用吗?

可以在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()返回A而不是B 您可以通过覆盖BsetA() ,调用setB()并返回B而不是A来使其工作。 为了避免委托每种方法,重点是。 我只想要一个可扩展的可链接类方法组,可以按任何顺序调用它们。 B.getFactory().B("b").A("a")显然有效。

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

在顶级class(A):

 protected final  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 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  _ a(K value) {//Note that this is static return new _(value);//So the IDE names aren't nasty } } 

连锁的助手类。

 /** * An instance method cannot override the static method from Chain, * which is why this class exists (ie to suppress IDE warnings, * and provide fluent usage). * * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved. */ final class _ { 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  _ a(K value) { return new _(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 like usage”,它是1或2行vs 8或更多。

书b = null; 
发布者p = null; 
 List books = null; 
 String id =“Melnibone的Elric”;

 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()));

VS:

书b = null; 
发布者p = null; 
 List books = null; 
 String id =“Melnibone的Elric”;

 b = findBook(id);
 Array [] books = null;
 if(b!= null){
     p = b.getPublisher();
     if(p!= null){
         books = p.getPublishedBooks();
     }
 } 

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

没有NPE,如果链条完成,你会得到“Melnibone的Elric”出版商出版的所有书籍(即“Ace”出版商出版的所有书籍),如果没有,你会得到一个空的。

我相信有一种方法可以用generics做到这一点 ……语法比想要的要简洁一点……

这是客户端代码……

  B b = B.factoryB(); b.setA("a").setB("b"); A ba = A.factoryA(); ba.setA("a"); 

顶级(真实)课程

 public class A extends Chained { private String a = null; protected A() { } public S setA(String a) { this.a = a; return me(); } public static A factoryA() { return new A(); } } 

示例子类

 public class B extends A { private String b = null; B() { } public S setB(String b) { this.b = b; return me(); } public static B factoryB() { return new B(); } } 

帮手

 public abstract class Chained { // class should be extended like: // ... class A extends Chained public Chained() { } public final S me() { return (S) this; } } 

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

如果可以访问源代码,通过扩展Alan写的内容,我会添加补充类来隐藏generics,同时允许inheritance和非常紧凑的语法。 BaseA和BaseB执行层次结构,而A和B执行隐藏generics。

 BaseA +- A +- BaseB +- B public class 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 { public static A factoryA() { return new A(); } } public class BaseB> extends BaseA { private String b = null; protected BaseB() { } @SuppressWarnings("unchecked") public S setB(String b) { this.b = b; return (S) this; } } public class B extends BaseB { public static B factoryB() { return new B(); } } public class Main { public static void main(String[] args) { B.factoryB().setA("").setB("").setB("").setA("").setA(""); } } 

流畅的界面与您已有的常规命令查询方法不同。 关注点的分离使得将它们分开是一个好主意。

因为你有一个现有的代码层次结构:写一个流畅的外观,为你做脏工作。

另请参阅Martin Fowler:域特定语言,4.2:对解析层的需求。