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処理が重い。。という意味で開発しづらいです。この辺はそのうち改善されるかもしれないけど。