インターフェースの押しつけはフレームワーク設計者のエゴだ
ブログさぼってますが、元気です。
S2Swing の kaiseh さんのところでの議論がけっこう面白いので、こちらでも少し書きます。
簡単におさらいすると、フレームワークを作るとき、フレームワークからユーザのつくったオブジェクトを呼び出す場合、3つの方法があります。
- インターフェース(もしくは抽象クラス)を提供して、ユーザ側で実装してもらい、FWそのインターフェースを叩く。(もっともオーソドックス)
- 規約に沿ったメソッド名をユーザ側で実装してもらい、FWはリフレクションで規約を調べてメソッドを叩く。(Teedaとかで採用しているパターン)
- メソッドにアノテーションをつけておき、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 によるフィールドへのインジェクションを引数にまで適用しただけなんですが、実際にサンプルアプリを作ってみると、思いのほか便利なのです。