No Programming, No Life

プログラミング関連の話題や雑記

Groovyならインタフェースはクロージャとマップで実装できる

はじめに

インタフェースは機能仕様と実装を切り離す素晴らしいアイデアです。Javaではinterfaceキーワードでこの機能を提供しています。が、Javaだとこれを実装するのが意外と面倒くさいんですよね。

Javaだとこうなる

Readable r = new Readable(){
  int read(java.nio.CharBuffer cb) {
    cb.put("12 34");
    return 5;
  }
}

いわゆる無名インナークラスでその場実装した例です。
これがGroovyだと以下のような二つの方法で簡単に実装が可能です。

その1. クロージャで実装

def r = {
  it.put('12 34'); 5
} as Readable
解説

{ it.put('12 34'); 5 } の部分がクロージャですね。これを as Readable することによりインタフェース実装に早変わり。

あれ?でもインタフェース定義ってメソッド名とか合わせなくてもいいだっけ?

上記の例の場合Readableは実装すべきメソッドが一個だけのインタフェースなので、実装するクロージャ内は暗黙的にそのメソッドの実装を書くことになり、itがそのメソッドの引数になります。*1

じゃあ複数のメソッドを要求してくるインタフェースの場合はどうすればいいの?

その場合は、クロージャの引数をObject[]型にすればよいです。

interface X {
  void f()
  void g(int n)
  void h(String s, int n)
}

x = {Object[] args -> println "method called with $args"} as X
x.f()          // => method called with null
x.g(1)         // => method called with [1]
x.h("hello",2) // => method called with [hello, 2]

すべてのメソッド呼び出しはこのクロージャ呼び出しに変換されるので、あとは引数のObject配列の内容を判断して処理を選り分ければいいわけですね。
ただこの方法ですと、要求されるメソッドが多いインタフェースの場合はクロージャ内の実装がif-elseもしくはswitch-caseの嵐になってしまいそうです。この方式を使う場合、実装するメソッドが少なくて済む場合に限ったほうが良さそうです。

その2. マップで実装

要求するメソッドが多いインタフェースの場合はキーにメソッド名、バリューにクロージャを格納したマップで定義してあげるといい感じになります。

interface X {
  void f()
  void g(int n)
  void h(String s, int n)
}

def x = [
  f: { ->      println "method f called"             },
  g: { n ->    println "method g called with $n"     },
  h: { s, n -> println "method h called with $s, $n" },
] as X

x.f()          // => method f called
x.g(1)         // => method g called with 1
x.h("hello",2) // => method h called with hello, 2
補足

実はマップのキーはすべてのインタフェースが要求しているメソッド名を取り揃えていなくてもよいみたいです。ただ、インタフェースがそのメソッドを呼び出そうとして実装がなかった場合は例外が発生してしまうのでご注意を。

おわりに

Groovyでインタフェース実装をちゃちゃっと書きたいなと思った場合はぜひこの方法を使ってみて下さいね。

*1:要するにitがCharBuffer型になるわけです。