インターフェースの押しつけはフレームワーク設計者のエゴだ

ブログさぼってますが、元気です。

S2Swing の kaiseh さんのところでの議論がけっこう面白いので、こちらでも少し書きます。

簡単におさらいすると、フレームワークを作るとき、フレームワークからユーザのつくったオブジェクトを呼び出す場合、3つの方法があります。

  1. インターフェース(もしくは抽象クラス)を提供して、ユーザ側で実装してもらい、FWそのインターフェースを叩く。(もっともオーソドックス)
  2. 規約に沿ったメソッド名をユーザ側で実装してもらい、FWはリフレクションで規約を調べてメソッドを叩く。(Teedaとかで採用しているパターン)
  3. メソッドにアノテーションをつけておき、FWはアノテートされたメソッドを呼び出す。(Urumaとかで採用しているパターン)

最近流行ってる(のか?)、「規約ベース」の考え方が2ですね。

いろいろ考えましたが、わたしのイチオシはやはりアノテーションです。

アノテーションの一番大きなメリットは、メタ情報を付与できること。たとえば Uruma では、GUI上で発生したイベントをPOJOで拾う場合、@EventListener アノテーションをメソッドに書きます。

@EventListner
public void onSelect(){
 ...
}

みたいな感じで。

さらに、発生元のウィジットやイベントの種類を指定したい場合は、こんな感じでアノテーションの引数にかけます。

@EventListner(id="table", type="selection")
public void onSelect(){
 ...
}

このあたりはインターフェースにはできない芸当です。アノテーションに指定するパラメータに列挙型を使えばIDEによる補完がつかえるので、プログラマの負担も少ない。

あと、心理的にインターフェースの場合は「さぁ、これから作るぞ」って感じでちょっと身構えるのですが、アノテーションの場合は先にメソッドを書いてから「これはこのタイミングで呼び出してほしい」という感じで気軽に付けられます。

ここで id:kaiseh さんから、「アノテーションだと呼び出すメソッドの引数が決まらないから、IDEで補完しにくいよ〜」というご意見をいただいたのが、あらすじです。

で、ここからが本題。

インターフェースってのは、そもそもAPIの仕様を外部に公開して「呼び出してもらう」ために存在するわけです。それをフレームワークがユーザアプリケーションを呼び出すときにインターフェースを押しつけて「呼び出してあげるから、こーいうふうにクチを開けて待ってなさい」というのは、よくよく考えてみればなんとエラそうなことか。

そこで Uruma 0.3 では、このあたりをもう少し親切にしています。

たとえば、テーブルで行を選択したときにユーザアプリケーションが呼び出されるようなシチュエーション。

たいていの場合、呼び出される側は選択行に関する情報が欲しいわけです。どんな情報かというと、選択行に紐付いたモデルオブジェクト

たとえば、ファイルの一覧を表示しているテーブルでは、各行に対応するモデルオブジェクトは File クラスになるでしょう。呼び出される方は選択行に対応した File オブジェクトが欲しいのです。

で、それは単一行選択ならば

public void onSelect(File file){
 ...
}

で呼び出してほしいし、複数行なら

public void onSelect(File[] files){
 ...
}

とか

public void onSelect(List<File> files){
 ...
}

という感じで配列やリストで渡して欲しいというのが人情です。

Uruma では、メソッドのシグネチャをある程度判断して、このあたりを「いい感じ」に渡してくれます。
ここまでくると、インターフェースでは堅すぎて使えないのです。

ようするに @ImportValue によるフィールドへのインジェクションを引数にまで適用しただけなんですが、実際にサンプルアプリを作ってみると、思いのほか便利なのです。