rubyのmix-in
rubyにはJavaと同じようにクラス階層がある。が、rubyにはモジュールという概念もある。
簡単に言うと、クラス階層を横断して機能を再利用するための仕組みといったところでしょうか。
クラスに組み込んで(mix-in)して使うもので、1つのクラスに対して複数のモジュールを組み込むことが出来ます。
Enumerableモジュールの組み込み
あらかじめ用意されているモジュールとして有名なのがEnumerableモジュール。繰り返し処理を汎用化させたモジュールで、rubyのArrayクラスも組み込んでいる。
もちろん、自分で定義したクラスにも組み込むことが出来る。
例えば次のような関係にあるクラスがあったとする。
1 0..* Container ----------- Content
クラス定義はこんな感じ。Container内部ではArrayでContentを持っているだけです。
class Container def initialize() @contents = [] end def add(content) @contents << content end end class Content attr_reader :str, :num def initialize(str="a", num=1) @str = str @num = num end end
このContainerにEnumerableモジュールを組み込むには次のようにincludeを使う。Enumerableモジュールを組み込む際はeachメソッドの定義が必要。
class Container include Enumerable def each @contents.each{|content| yield content } end ・・・略・・・ end
実行例。
container = Container.new c1 = Content.new c2 = Content.new("a1", 2) c3 = Content.new("b", 3) #ばらばらに追加 container.add(c3); container.add(c1); container.add(c2); findedContent = container.find{|c| c.str == "b"} # c3が返る sortedContents = container.sort{|a,b| a.num <=> b.num} # c1,c2,c3の順に並ぶ
自作モジュールの定義
次に自分でモジュールを定義してみたいと思います。
足し算機能をもったAddモジュールの定義は以下のように行います。
module AddModule def add(a,b) a+b end end
で、クラスに組み込む。
class Sample include AddModule end
実行。
puts Sample.new.add(1,2) #3が返る
なお、クラスではなく、インスタンスに組み込むことも出来ます。この場合はextendを使います。
ary = [] ary.extend(AddModule) puts ary.add(1,2) #3が返る
クラスを横断して機能追加を行う仕組み、Javaには無いかと思いきや・・・あるんです、アスペクト指向プログラミングですね。
Javaによる実装
最後のAddModuleの例をAspectJを使ってJavaで実装してみると次のようになります。
public aspect AddAspect { public int Sample.add(int a, int b) { return a+b; } }
対象のクラス
public class Sample{ }
実行。
System.out.println(new Sample().add(1,2)); // 3が返る
一時期アスペクト指向って騒がれたけど、イマイチ流行ってないような。。Seasar2とかSpringと併用して使ってます、っていうのは多いかもしれないけど。
おそらく、理由は3つあるのではないかと思ってます。
- Java標準ではない
モジュールのようにrubyの仕組みとして組み込まれていない、という点で不安
- どんな動きになっているか追いかけにくい
rubyのモジュールはクラスが取り込むことを宣言している。一方、AspectJの場合、対象のクラス側は全然そういったコードが出現しない。横断的関心事を既存クラスから分離するという考え方からいくと正しい。これがいい点なんだけど、気づいてない人からすると、なんでこんな動きなの?となって分かりにくい。
- 開発しづらい
このインタータイプ宣言ですが、eclipse上ではコンパイルエラーみたいになるんですよね。addなんてメソッドはSampleに無いって。正直鬱陶しいし、赤×マークがずっと出てる状態で開発を行うのは精神衛生上よくない。
それに、ポイントカットの定義をしている場合、既存のクラス数が多くなってくるとコンパイルする時のwoven処理が重い。。という意味で開発しづらいです。この辺はそのうち改善されるかもしれないけど。