7.1 モジュールの定義と使い方
前節に説明した継承を使うことでクラスに機能を追加することは可能だが、以下のような多重に継承することは禁止されている。
class Foo
def initialize
@x = 'abc' # フィールドxはString
end
def method_a
puts @x
end
end
class Bar
def initialize
@x = 123 # フィールドxはFixnum
end
def method_a
puts @x
end
end
class Fizz < Foo < Bar
def initialize
super('abc') # どちらのコンストラクタを呼べばいいかわからない。
end
def method_b
method_a # どちらのmethod_aが呼び出して良いかわからない。 @x はどちらのクラスのフィールド?
end
end
多重継承ではフィールドやメソッドが同名の場合、どの親クラスのものか 特定できない(特定する方法があったとしても複雑な仕組みになってしまう。)。 クラスの継承関係が複雑になってしまう(親が一意に特定できない)ため多重継承を意図的にサポートしない言語がほとんどである。この問題は菱形継承問題などと名前が付けられているので興味があれば調べてみるとよいだろう。(https://ja.wikipedia.org/wiki/菱形継承問題)
そこでRubyではモジュールという追加したい機能を自由に選択し、クラスに付与するための機構が用意されている。以下がモジュールの例である。
module Foo
def method_a
@x * 2
end
def method_c
"This is a Foo's instance method"
end
end
module Bar
def method_b
@x + 10
end
def method_c
"This is a Bar's instance method"
end
end
class Fizz
include Foo, Bar
def initialize
@x = 123
end
end
fizz = Fizz.new
fizz.method_a()
=> 246
fizz.method_b()
=> 133
fizz.method_c()
=> "This is a Foo's instance method"
Fizz.ancestors
=> [Fizz, Foo, Bar, Object, Kernel, BasicObject]
上の例では、モジュールFoo, Barが定義されクラスFizzにincludeキーワードによって取り込まれている。 クラスがモジュールを取り込むことをMix-in(ミックスイン)と呼ぶ。 モジュールの定義の仕方はクラスと同様である。method_a,method_bは、それぞれモジュールFoo, Barで定義されて、ミックスインされたあと無事Fizzクラスのインスタンスで呼び出すことができている。それでは多重継承で問題となっていた同名のフィールドやメソッドはどのように解決されているのだろうか。まず、同名のメソッドであるmethod_cを呼び出すと、Fooモジュールで定義されたmethod_cが呼び出されていることがわかる。なぜこのような呼び出しになるかを確かめるには、クラスの継承関係を確認する必要がある。全てのクラスにはancestorsというクラスメソッドが用意されているので呼び出すと、継承されたクラスとミックスインされたモジュールの一覧が配列の形になって返ってくるのがわかる。 Rubyのミックスインでは、親が複数あると言うアプローチではなく線形的に親子関係を表すことで同名メソッドを解決している。つまり配列の先頭から見ていき、自身のクラス(Fizz)の次のモジュールFooがmethod_cを持っていたため、Fooモジュールのmethod_cが採用されたというわけである。一方Barモジュールのmethod_cを採用するにはincludeキーワードのミックスイン順序を逆にすれば良い。
次に、以下のようにモジュールをインスタンス化を試みて欲しい。
Foo.new
<main>': undefined method `new' for Foo:Module (NoMethodError)
すると以上のようなエラーがでる。モジュールはインスタンス化ができないという規則にすることで クラスの多重継承とは異なるものだと理解できるだろう。フィールドxはモジュール自身のインスタンス変数ではなく、モジュールをミックスインしたクラスのインスタンス変数として一意に定まるので同名フィールド問題も解決している。
モジュールについてまとめると以下のようになる。
- モジュールはインスタンス化できない
- モジュールはクラス(もしくは別モジュール)に対して複数ミックスインすることができる
- 菱型継承問題を線形的にすることで、同一フィールド、メソッド、複雑な継承関係を解決している
- モジュールはミックスインする順序が大切である