読者です 読者をやめる 読者になる 読者になる

100%ピュアJavaでダックタイピングを実現してみたら、違う何かになっていた・・・

java

2013/9/29 追記

CodeReposがなくなってたのでgithubにアップしておきました。

今回のポイント

  • 100%ピュアJava
    • 動的言語を裏で呼び出すとか、そんなことはしません。普通にJavaのみ。
  • デフォルトのライブラリのみ
  • Javaっぽく
    • メソッド名を文字列で渡すとか、そんなんJavaのメソッド呼び出しじゃない!

オレオレ式ダックタイピングのイメージ

なんだかよくわからんが、鳴けといったら「ガー」と鳴いた。ゆえにそれはアヒルだ。

というのがダックタイピングですね(超イイ加減)。


インタフェースとの比較で考えてみます。

  • インタフェース
    • 利用される側が規定し、実装する。
      • 「みんな!このインタフェース実装しようぜ!使う人のためにさ!おれたちががんばるんだ!」
    • 利用する側は、決まっているAPIを呼ぶ。間違ったらコンパイルエラーで検出可能。
      • 「ふーん。要は決められた通り使えばいんでしょ?」
  • ダックタイピング
    • 利用される側は利用する側の都合なんて気にしない。POJOで実装すればいいじゃない。
      • 「おれはおれのやりたいようにやるさ」
    • 利用する側は、こんなメソッドあるでしょ?みたく手探りで呼び出す。一致したら実行できる。
      • 「ダメもとで呼んでみるよ。ダメなときはそんとき考えればいいさ」

こんなイメージね。
Javaはインタフェースバリバリ。
使う側で楽をするために、ライブラリ側でがんばりますっていかにもJavaっぽい感じ。
でも共通インタフェースのないクラスのメソッドを順繰り呼ぼうとすると大変だよね。


そんなとき、動的言語のダックタイピングに、なんか、ちょっと憧れたりして。
ちょっとだけ、ね。

Javaでダックタイピングといってありがちな例

よく見かけるのが、リフレクションをそのまま使ったようなこんな記法*1

class PojoObject {
    public String hoge() {
        System.out.println("method:hoge()");
        return "return:hoge()";
    }
}

Object pojo = new PojoObject();
ReflectionUtils.invoke(pojo, "hoge")

うーん。かっこ悪い。
だいたい、普通のJavaのメソッド呼び出しと全然違うし。


でも、仕方が無い。
一般的にはJavaで動的に何かをするなら、Stringで指定するしかない。
対象クラスが実装していないメソッド呼び出しなんかかけないし。


........仕方が無い。



............本当に?



↓こんな風に普通のメソッド呼び出しっぽく書けたら、ちょっとカッコイイと思わない?

pojoをごにょごにょする何か.hoge();


........そう、できたんです。

Javaっぽいダックタイピング記法

class PojoObject {
    public String hoge() {
        System.out.println("method:hoge()");
        return "return:hoge()";
    }
}
interface HogeMethod { String hoge(); }

Object pojo = new PojoObject();
Inject.<HogeMethod>to(pojo).hoge();

使う側が、

  • 自分の呼び出したいメソッドシグネチャを持つインタフェースを『勝手に』定義して、
  • それを『インジェクション』して、
  • そして普通にメソッド呼び出しする。


どう?Javaっぽくないですか?


コード的な何か

実はたいしたことはしてません。
記法自体はGenericsを使っただけです。
ポイントといえば、id:dewaさんのトリッキーコードによるClassの取得ですね。


Inject.to()が返すオブジェクトは、Proxyオブジェクトです。
今回、はじめてProxy/InvocationHandlerを使ってみましたが、なんという変態的な仕組みでしょう!面白すぎます。


一応CodeReposにコミットしてみたので、万が一興味があるような方がいらっしゃれば、ソースをどうぞ。

意味的な何か

ダックタイピングといいつつ、実はこれちょっと違うような気がします。

利用される側で規定して実装するのがこれまでのインタフェースの使われ方でした。

これが、今回のように『利用する側で規定して、インスタンスに注入する』ことができるわけです。

これが本当の『インタフェース・インジェクション』ですね。

インタフェース型の変数だったらDIする、みたいな意味のインタフェース・インジェクションではなく、文字通り『インタフェースをインジェクションする』。


今後の展開

上記のような記法でメソッドの呼び出しはかけます。コンパイルエラーはでません。
後はInjectユーティリティの向こうで何をやるか、という話であって、バイトコードをいじっちゃえば実は結構いろんなことができるんじゃないかなぁーと思います。


今のところ、Interfaceしかインジェクションできませんが*2、BCEを使えばS2Daoみたいな抽象クラスの抽象メソッドをインジェクションしたりもできそうですね。


あと、オシャレな感じでmethod missingを実現しようとすると、BCEが必要かも知れませんね。
今は単にMethodMissingRuntimeExceptionをスローするだけで、キャッチしてどうにかするぐらいの普通な感じです。


うーん。応用例はよくわかりません。なんかあるかなぁ。一発ネタかなぁ。


きっかけ

会社の業務的にGroovy/Grails漬けの今日この頃なのですが、動的言語は面白いなぁーと思いながら会社帰りに歩いていたら、「Javaでダックタイピング」というフレーズが頭に浮かんで、例の記法を妄想して、帰って書いてみたら動いた、という訳。


というわけで、とりあえず書いてみて面白かったので、公開してみました。
既出だったら、華麗にスルーしてくださいね。

まとめ

もういちどまとめておきます。

  • ダックタイピング、ならぬ、インタフェース・インジェクション。
  • Genericsは変態(ビミョウな意味で
  • Proxy/InvocationHandlerは変態(いい意味で

[追記]ソースへの直接リンク

結構深くて気軽に見れなさそうなので、主要クラスへの直リンおいときますね。
といっても2個で十分なんですけど。

と思いましたが、使い方を知るにはテストを見るのが一番ですねということで、もうひとつ追加。

*1:ReflectionUtilsの実装は推して知るべし

*2:Proxy/InvocationHandlerを使ってるため