Can one create an extensible class hierarchy in java whose methods are fluent and can be invoked in any order? (YES! see answer below), even for existing classes wh
The answer (to my surprise and satisfaction) is YES. I answered this question myself: You can do this with a little work if the method invocations return instances of the class in question (see chainable below). I also found an even easier way do this if you can edit the top level source:
In the top level class (A):
protected final T a(T type) {
return type
}
Assuming C extends B and B extends A.
Invoking:
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");
Example 1 and 3 are ways to make a existing class hierarchy fluent and to allow methods to be called in any order so long as the existing classes are fluent (but you don't have access to or can't change the source). WAY2 is an example where you do have access to the source, and want the calls to be as simple as possible.
Full 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;
}
}
Test class 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() + "]";
}
}
Test class 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();
}
}
Test Class 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();
}
}
The Chain class
/**
* 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
}
}
Chain's helper class.
/**
* 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 _ {
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);
}
}
Output:
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. :)
I've not ever seen anyone do this; I think it could be a new and potentially valuable technique.
P.S. With regard to the "elvis like usage", it is 1 or 2 lines vs 8 or more.
Book b = null; Publisher p = null; List books = null; String id = "Elric of Melnibone"; 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:
Book b = null; Publisher p = null; List books = null; String id = "Elric of Melnibone"; 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()));
No NPE, and if the chain completes you get all the books published by the publisher of "Elric of Melnibone" (i.e. all the books "Ace" publishers has published), and if not you get a null.