Edited at

Rubyの継承階層におけるmoduleの位置関係を解説する。@Effective Ruby

More than 1 year has passed since last update.


概要

Rubyistの為のベストプラクティス集として有名なEffective Rubyの内容の中でも

初見で理解できないであろう

"モジュールはMix-inされるとそのクラスのすぐ上に特異クラスとして定義される"

という話をわかりやすいコードで説明します。


まずはモジュール無し

継承階層をごく簡単に復習するために、まずはこちらのコードを御覧ください。


sample.rb

class Parent

def feature_a
puts "this is a feature_a method from Parent class"
end
end

class Child < Parent
def feature_a
super
end
end

ins = Child.new
ins.feature_a #=>this is a feature_a method from Parent class



  1. Parentクラスを定義する。クラス内にはインスタンスメソッドのfeature_aを定義しておく。

  2. Parentクラスを継承したChildクラスを定義する。クラス内でParentクラスのfeature_aメソッドをオーバーライドし、superによって親クラスの同名メソッドを呼び出す。

  3. Childクラスのインスタンスを生成し、メソッドを呼び出すと、Parentクラスのfeature_aメソッドが呼び出されていることがわかる。

Effective Rubyに手を出している方には簡単なコードかもしれませんね。

では、このChildクラスにモジュールをミックスインするとどうなるか。



sample.rb

module CoolFeatures

def feature_a
puts "this is a feature_a method from CoolFeatures module"
end
end

class Parent
def feature_a
puts "this is a feature_a method from Parent class"
end
end

class Child < Parent
include CoolFeatures

def feature_a
super
end
end

ins = Child.new
ins.feature_a #=>"this is a feature_a method from CoolFeatures module"


おや、スーパークラスはParentクラスのはずなのに、

インスタンスが呼び出したメソッドはCoolFeaturesモジュールのメソッドのようです。

これが、本書籍内で説明されている、


モジュールのミックスインによって対象クラスの上に特異クラスが挿入される

ということですね。


更にモジュール内のメソッドでもsuperを記述すると…


sample.rb

module CoolFeatures

def feature_a
super
end
end

class Parent
def feature_a
puts "this is a feature_a method from Parent class"
end
end

class Child < Parent
include CoolFeatures

def feature_a
super
end
end

ins = Child.new
ins.feature_a #=>"this is a feature_a method from Parent class"


これで完全に理解できましたね。

Childクラスで親のメソッド呼び出し



挿入された特異クラス(CoolFeaturesモジュール)のメソッドが呼び出される



そのメソッド内で親のメソッド呼び出し



Parentクラスのメソッドが呼び出されて、文字列出力される。

どうですか?

Rubyの継承階層はこれでだいたい理解できたのでは?

無題.png


おまけ initializeメソッドについても調べる。


sample.rb


class C1
def initialize(myname)
puts "this is a initialize method from C1 class"
end
end

module M1
def foo(val)
puts "this is a method from M1 module"
puts val*2
end
end

class C2 < C1
include M1

def foo(num)
super
end
end

ins2 = C2.new("Bob")
ins2.foo(30)


サブクラスにinitializeメソッドを定義しなかった場合、

なんとスーパークラスのinitializeメソッドが呼び出されます。

これはぜひ知っておくべきですね。

特に継承なんていうのはもはやほとんどのプロダクトで使うと思いますし、

initializeメソッドが"初期化"っていう名前がついている理由もより鮮明にわかりました。