kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Re: 流れるようなインターフェイス+ジェネリクス

出来るよ!

class Rows {}

interface Query<E extends Query<E>> {
    E findAll(String str);
    E where(String pred);
    E orderBy(String col);
    E limit(int n);
    
    Rows execute();
}

class MyQuery<E extends MyQuery<E>> implements Query<MyQuery<E>> {
    public MyQuery<E> findAll(String str) { return this; }
    public MyQuery<E> where(String pred) { return this; }
    public MyQuery<E> orderBy(String col) { return this; }
    public MyQuery<E> limit(int n) { return this; }
    
    public Rows execute() { return new Rows(); }
    
    public MyQuery<E> whereBetween(String col, int from, int to) { return this; }
}

public class Main {
    public static void main(String[] args) {
        MyQuery q = new MyQuery().findAll("stocks")
                                 .where("price >= 1000")
                                   .whereBetween("price", 10000, 20000)
                                 .orderBy("name")
                                   .limit(10);
        Rows rows = q.execute();
    }
}
http://d.hatena.ne.jp/bleis-tift/20081113/1226548493

Javaは1.5からメソッドのオーバーライド/実装時に、返り値の型をcovariantにすることができるので、Genericsを使わずとも、単に

class Rows {}

interface Query {
    Query findAll(String str);
    Query where(String pred);
    Query orderBy(String col);
    Query limit(int n);
    Rows execute();
}

class MyQuery implements Query {
    public MyQuery findAll(String str) { return this; }
    public MyQuery where(String pred) { return this; }
    public MyQuery orderBy(String col) { return this; }
    public MyQuery limit(int n) { return this; }
    
    public Rows execute() { return new Rows(); }
    
    public MyQuery whereBetween(String col, int from, int to) { return this; }
}

public class Main {
    public static void main(String[] args) {
        MyQuery q = new MyQuery().findAll("stocks")
                                 .where("price >= 1000")
                                   .whereBetween("price", 10000, 20000)
                                 .orderBy("name")
                                   .limit(10);
        Rows rows = q.execute();
    }
}

とすれば良いのではと思います。

あと、これはJavaで実装する場合には本質的には解決しようが無い問題だと思いますが、Queryが多数のメソッドを持っているクラスであるような場合に、型を合わせるためだけにいちいち多数のメソッドをオーバーライド/実装しなくてはならないのは面倒なところですね。

ちなみに、Scalaだとthis.typeという表記を使うことによって、サブクラス化した場合に、オーバーライドせずとも、そのサブクラスの型を返すようにすることができます。

class Rows

trait Query {
    def findAll(str: String): this.type = this
    def where(pred: String): this.type = this
    def orderBy(col: String): this.type = this
    def limit(n: Int): this.type = this
    def execute: Rows = new Rows
}

class MyQuery extends Query {
    //findAllやwhereなどをオーバーライドする必要が無い
    def whereBetween(col: String, from: Int, to: Int): this.type = this
}

object Main {
  def main(args: Array[String]) {
        val q = new MyQuery().findAll("stocks")
                             .where("price >= 1000")
                               //whereがちゃんとMyQuery型を返している
                               .whereBetween("price", 10000, 20000)
                             .orderBy("name")
                               .limit(10)
        val rows = q.execute
        ()
  }
}                           

追記:
Scalaのコードの後半部分が途切れていたので、追加。