Saturday, April 17, 2010

Fluent Interface Inheritance

Fluent Interface is a convenient way of writing DSLish code in the general-purpose programming language. Some of the examples are quite impressive. But there comes a problem when you try to reuse this pattern via inheritance, because the basic method chaining implementations do not actually support it very well. I will use a JPA example found in Wikipedia to demonstrate the problem:

public Collection<Student> findByNameAgeGender(String name, int age, Gender gender) {
return em.createNamedQuery("Student.findByNameAgeGender")
.setParameter("name", name)
.setParameter("age", age)
.setParameter("gender", gender)
.setFirstResult(1)
.setMaxResults(30)
.setHint("hintName", "hintValue")
.getResultList();
}

Looking at this code we can now create a naive Query implementation:

public interface Query {
Query setParameter (String name, Object value);
Collection getResultList() throws SqlException;
}

public class PreparedSqlQuery implements Query {
protected Map params = new HashMap();
public Query setParameter (String name, Object value) {
params.add(name, value);
return this;
}
public Collection getResultList() throws SqlException {
// Execute query and return results
}
}

public class SimpleQueryUser {
public static void main(String[] args) throws Exception {
new PreparedSqlQuery().setParameter("name", "value").getResultList();
}
}

No problems at all, our Fluent Interface is clean and elegant. But what happens when you create a subclass for executing prepared statements? You don't want to implement all that multiple setXxx methods once again, and instead decide to inherit from PreparedSqlQuery:

public class StoredProcedureCall extends PreparedSqlQuery {
protected String outputParameter = null;
// Stored procedures may have both input and output parameters
public StoredProcedureCall setOutputParameter(String param) {
outputParameter = param;
}
public Collection getResultList() throws SqlException {
// Execute stored procedure and return results
}
}

public class SimpleQueryUser {
public static void main(String[] args) throws Exception {
// This works fine
new StoredProcedureCall().setOutputParameter("OUT").setParameter("name", "value").getResultList();
// This produces compilation errors
new StoredProcedureCall().setParameter("name", "value").setOutputParameter("OUT").getResultList();
}
}

The second oneliner produces a compilation error because Query object returned by setParameter() does not actually have setOutputParameter() method. To address this problem we can use return type covariance, which is a language feature added to Java 5. Here goes a fixed version of our StoredProcedureCall:

public class StoredProcedureCall extends PreparedSqlQuery {
protected String outputParameter = null;
public StoredProcedureCall setOutputParameter(String param) {
outputParameter = param;
}
// Compiler allows us to use StoredProcedureCall instead of Query as the return type here because StoredProcedureCall implements Query
public StoredProcedureCall setParameter (String name, Object value) {
super.setParameter(name, value);
return this;
}
public Collection getResultList() throws SqlException {
// Execute stored procedure and return results
}
}

Actually you can see exactly the same approach used in the real-world Query implementations, like OpenJPAQuery. Now what if you don't want to override all that numerous setXxx methods in all subclasses just to change their return types? Here is my solution utilizing another Java 5 language feature - Generics:

public interface Query <T extends Query >{
T setParameter (String name, Object value);
Collection getResultList() throws SqlException;
}

public class PreparedSqlQuery<S extends PreparedSqlQuery> implements Query<S> {
protected Map params = new HashMap();
public S setParameter (String name, Object value) {
params.add(name, value);
return (S) this;
}
public Collection getResultList() throws SqlException {
// Execute query and return results
}
// Hide constructor, use factory to ensure users won't try to do something like: new PreparedSqlQuery<StoredProcedureCall>().setOutputParameter
}

// We make it generic too to enable further extension
public class StoredProcedureCall<R extends StoredProcedureCall> extends PreparedSqlQuery<StoredProcedureCall> {
protected String outputParameter = null;
public R setOutputParameter(String param) {
outputParameter = param;
return (R) this;
}
// Note we don't have to override setParameter here
public Collection getResultList() throws SqlException {
// Execute stored procedure and return results
}
}

public class EntityManager {
public static StoredProcedureCall<StoredProcedureCall> createStoredProcCall() {
return new StoredProcedureCall<StoredProcedureCall>();
}
public static PreparedSqlQuery<PreparedSqlQuery> createNamedQuery() {
return new PreparedSqlQuery<PreparedSqlQuery>();
}
}

public class SimpleQueryUser {
public static void main(String[] args) throws Exception {
EntityManager.createStoredProcCall().setParameter("name", "value").setOutputParameter("OUT").getResultList();
}
}

All that trickery with generics is done to fool the compiler. Good thing about it is that no additional methods are created, that means there are no super calls, which can potentially improve runtime performance. Bad things are obvious too:
  1. It is not safe unless you are using factories to instantiate that builders;
  2. It is definitely not obvious and looks ugly;


Disclaimer: this is a hack, never use it in your code :)