Wir bauen ein Chamäleon

Java macht es einem nicht gerade leicht, DSLs zu schreiben und dabei typsicher zu bleiben. Angenommen, wir wollen eine DSL zum Erstellen von SQL-Abfragen in der Form select(„id“).from(„person“).where(„age > 18“).and(„name like ‚Daniel %'“) basteln: Auf der einen Seite wollen wir vermeiden, einen eigenen Typ für jeden Auswertungsschritt zu erstellen, auf der anderen Seite darf natürlich nicht erlaubt sein, das and() direkt hinter dem select() oder from() aufzurufen, oder from() zweimal u.s.w. Eine Technik, die genau das erlaubt, nenne ich „das Chamäleon“: Unser Objekt bleibt immer das gleiche, schmückt sich aber mit verschiedenen Interfaces. Solange der Nutzer artig ist und nicht castet, ist damit die Typsicherheit gesichert. Und nun etwas Butter bei die Fische:

public interface SelectQuery {
  public FromQuery from(String table);
}

public interface FromQuery {
  public WhereQuery where(String condition);
}

public interface WhereQuery {
  public WhereQuery and(String condition);
  public WhereQuery or(String condition);
}

public class Query implements SelectQuery, FromQuery, WhereQuery {
  private String[] columns;
  private String table;
  private String condition;

  private Query(String... columns) {
    this.columns = columns;
  }

  public FromQuery from(String table) {
    this.table = table;
    return this;
  }

  public WhereQuery where(String condition) {
    this.condition = condition;
    return this;
  }

  public WhereQuery and(String condition) {
    this.condition += " and " + condition;
    return this;
  }

  public WhereQuery or(String condition) {
    this.condition += " or " + condition;
    return this;
  }
  
  @Override public String toString() {
    String cols = java.util.Arrays.toString(columns);
    return "SELECT " + cols.substring(1,cols.length()-1) + " FROM " + table + 
           (condition == null ? "" : " WHERE " + condition);
  }

  public static SelectQuery select(String... columns) { 
     return new Query(columns); 
  }
}

Ein kleiner Test:

public class Test {
  public static void main(String[] args) {
    System.out.println(Query.select("id","name","age").from("person").
        where("age >= 18").and("name like 'Daniel %'")
    );  
  }
}

//--> SELECT id, name, age FROM person WHERE age >= 18 and name like 'Daniel %'

Sachen wie Query.select(„id“,“name“,“age“).where(„age >= 18“).from(„person“) kompilieren nicht. Natürlich ist das hier nur ein Spiel-Beispiel, eine „ordentliche“ DSL müsste schon etwas mehr leisten: GROUP, HAVING, ORDER BY, Unterabfragen, typsichere Ausdrücke statt Strings wie „age >= 18″… Und ich denke, dass sich nicht alle diese Kombinationen mit dem „Chamäleon“ lösen lassen. Trotzdem ist es ein nützliches Werkzeug, um Java ein bisschen gemütlicher einzurichten.

In Scala hat man es natürlich leichter, z.B. gibt es dort den SQL-Wrapper DBC, dessen neueste Version jedoch leider auf sich warten lässt. Aber schick aussehen tut es jedenfalls, wie man hier sehen kann.

Werbeanzeigen