うさぎ組

kyon_mm with software

このエントリーをはてなブックマークに追加

GroovyのMOPについて

はじめに

これはMOP Advent Calendar2013の記事になります。何度かGroovyのMOPについては発表しているのですが、ブログでも書いておきたかったので参加しました。

http://qiita.com/advent-calendar/2013/mop

MOPとはなにか?

このAdventCalendarの最初に「MOPって何?」で触れられています。

http://g000001.cddddr.org/3594812400

っていうのはありつつ、自分がよく説明する「何ができればMOPなのか」っていうと、次の2点を満たせていればいいのではないかという感じです。

  • オブジェクトとデータの振舞いを参照できること
  • オブジェクトとデータの振舞いを変更できること

Javaのリフレクションは参照はできても変更はできないわけですね。javaassistとか使えば。。。って感じでしょうか。Groovyは言語機能としてMOPを備えている感じです。

GroovyのMOPに関する継承関係

次のようなクラスの関係になっています。

MetaClassImpl,ExpandoMetaClass,etc,,, -> MetaClass -> MetaObjectProtocol

MOPとしてのAPIはMetaObjectProtocolインターフェースが定義していて、GroovyではそのAPIを○○MetaClassで実装として提供している感じになっています。Javaの参照型が全てObjectのサブクラスであるように、Groovyでは全てGroovyObjectSupportのサブクラスになっています。このGroovyObjectSupportがMetaClass(MetaObjectProtocol)を保持することで、MOPの機能を使うことが可能になっています。

なのでJava言語で書くクラスでもGroovyObjectSupportを継承すればMOPが使えるようになります。

GroovyのMOP-API

Closer to MOPと同じ感じでGroovyのMetaObjectProtocolインターフェースにAPIが定義されています。具体的には次のような感じです。

  • getProperties
  • getMethods
  • respondsTo
  • respondsTo
  • hasProperty
  • getMetaProperty
  • getStaticMetaMethod
  • getMetaMethod
  • getTheClass
  • invokeConstructor
  • invokeMethod
  • invokeMethod
  • invokeStaticMethod
  • getProperty
  • setProperty
  • getAttribute
  • setAttribute

実装としてはこれらをMetaClassの具象化クラスでいい感じに実装している感じになります。

GroovyのMOPを使う方法

基本的には<クラス名>.metaClass.<API> や <オブジェクト>.metaClass.<API>といった感じで呼びだします。<API>に未定義な名前を指定して代入すれば新しく定義できます。これは実行時に追加されます。追加したものが値であればプロパティとして、ClosureであればClosureとして追加されます。 クラス名に対して追加した場合は、その行が実行された後に実行されるそのクラスに関わる操作に反映されます。オブジェクトに対して追加した場合は、そのオブジェクトのみに反映され、他のオブジェクトには反映されません。

GroovyのMOPはデフォルトではMetaClassImplが使われますが、何かmetaClass経由で変更を加えると自動的にExpandoMetaClassに変更されます。ここらへんは特に大きく意識することはないとは思いますが、自分でMOPを使ってオブジェクトシステムの挙動を変えたいときは気にする必要がでてきます。

MOPのAPIを使う

未定義のメソッドが実行された時の挙動を制御するには次のメソッドをオーバーライドします。また、GroovyInterceptableを継承すると全てのメソッド実行時に起動されます。

  • Object invokeMethod(String methodName, Object args)

プロパティのget/setではそれぞれ次を使います。

  • Object getProperty(String propertyName);
  • void setProperty(String propertyName, Object newValue);

また、メソッドがなかったとき、プロパティがなかったときをフックしたい場合は次のメソッドを定義します。

  • Object methodMissing(string name, Object argument)
  • Object propertyMissing(String name)

注意はgetPropertyを実装するとpropertyMissingは起動しなくなることです。

他にもmetaClass経由でいわゆるJavaのリフレクションのように定義されているメソッド一覧を取得したりも先のAPIを使ってできます。

未定義なAPIを追加したり書き換える方法

オブジェクトに対して反映する例は次のような感じになります。

class Kyon{ def lines = "groovy is cool."}
def kyon = new Kyon()
assert kyon.metaClass =~ /(?i)metaclassimpl/
kyon.metaClass.say = {lines * it}

assert kyon.metaClass =~ /(?i)expandoMetaClass/

assert kyon.say() == "groovy is cool. groovy is cool. groovy is cool."

またstaticなプロパティやClosureを定義したい場合は次のようにstaticを挟めばよいです。

class Kyon{ def lines = "groovy is cool."}
def kyon = new Kyon()
kyon.metaClass.static.say = {lines * it}

またMOPでこのように追加する操作を同じ対象にいくつもやりたいときには次のようにちょっとだけ楽をできます。

String.metaClass {
    isShort { -> delegate.size() < 5 }
    isLong { -> 10 < delegate.size() }
}

assert  "1234".isShort()
assert !"12345".isShort()
assert !"1234567890".isLong()
assert  "12345678901".isLong()

基本的な使い方はこのくらいです。

GroovyのMOPの今後

GroovyはJava7のMethodHandlerやinvokedynamicがない時代からなんとかするためにこのような独自システムを持つわけでした。現在はJava7やJava8の機能を使ったMOPの設計にするように議論が進んでいるようです。