はじめに
「モジュールとクラス」というのは、Ruby について勉強する中で、初心者が躓く最初のポイントなのではないかなと思います。
私自身、学び始めの時は特にモジュールとクラスが良く分からず、色んなところでクラス継承や mixin が行われているとよく分からなくなってしまうことが多々ありました。
様々な文献に当たって少しずつ理解できてきましたが、そこでふと疑問に思ったことが「あるクラスにおいて、モジュールの mixin とクラス継承が行われていて、その mixin したモジュールと親クラスに同名のメソッドが定義されていた場合はどっちに定義されたメソッドが実行されるの?」というものです。
今回はそんな疑問について調べてみました。
結論
結論から言ってしまうと、
あるクラスにおいて、モジュールの mixin とクラス継承が行われていて、その mixin したモジュールと親クラスに同名のメソッドが定義されていた場合
このような場合は、そのメソッドは mixin したモジュールで定義されているような動作をします。その理由を以下で解説します。
コード例
たとえば、以下のような Human
クラスを定義して、self_introduction
メソッドを実行してみます。
class Human def self_introduction puts "I'm a human." end end human = Human.new human.self_introduction #=> I'm a human.
次に、その子クラスである PerfectHuman
クラスを定義して self_introduction
メソッドを実行してみます。
class Human def self_introduction puts "I'm a human." end end class PerfectHuman < Human def self_introduction puts "I'm a perfect human." end end perfect_human = PerfectHuman.new perfect_human.self_introduction #=> I'm a perfect human.
この場合、PerfectHuman
クラスで定義された self_introduction
メソッドが実行されます。
メソッドのオーバーライドというやつです。
次は、PerfectHuman
クラスでは何も定義しないで self_introduction
メソッドを実行してみます。
class Human def self_introduction puts "I'm a human." end end class PerfectHuman < Human end perfect_human = PerfectHuman.new perfect_human.self_introduction #=> I'm a human.
結果は、Human
クラスで定義された self_introduction
メソッドが実行されます。
ここまでは当たり前の結果かと思います。
では、次のはどうでしょうか。
module OrientalRadio def self_introduction puts "Na Ka Ta Nakata, I'm a perfect human." end end class Human def self_introduction puts "I'm a human." end end class PerfectHuman < Human include OrientalRadio end perfect_human = PerfectHuman.new perfect_human.self_introduction #=> ???
perfect_human.self_introduction
は何を返すのか。
「結論」でも述べたとおり、正解は以下の通りです。
perfect_human.self_introduction #=> Na Ka Ta Nakata, I'm a perfect human.
継承階層
これは、継承階層が以下のようになっているからです。
PerfectHuman クラス の上に OrientalRadio がいます。
このように描くと、少し混乱させてしまうかもしれません。
現に PerfectHuman.superclass
は Human
を返します。
puts PerfectHuman.superclass #=> Human
しかしこれは、特異クラスが無名で見えず、superclass
メソッド、class
メソッドでは出てこないためです。
特異クラスについてはこちらの記事を参照。
つまり、PerfectHuman
クラスが OrientalRadio
モジュールを include したことで、PerfectHuman
クラスのすぐ上に OrientalRadio
の特異クラスが作られたという訳です。
継承階層を正しく把握することのメリット
少し細かい話ですが、このような継承階層を正しく理解しておくことには、いくつかのメリットがあると考えています。
メソッドが自分の意図していない動作をすることを防ぐ
これは分かりやすくて、継承階層を理解していないと、自分が意図した通りに動作しない場合が起こり得るためです。
PerfectHuman
クラスが Human
クラスを継承していても、コード例のような場合には、Human
クラスのメソッドが実行されないということがあります。
モジュールを正しく実装できる
ところで「継承階層」の図を観て、不自然に感じなかったでしょうか?
PerfectHuman
の上に OrientalRadio
がいるのは少しおかしな話ではありませんか?
階層構造を正しく理解していれば、OrientalRadio
モジュールと Human
クラス・PerfectHuman クラスの抽象度が一貫していないことに気付かれると思います。
つまり、OrientalRadio
モジュールを後から定義するのは良い書き方とは言えず、後から定義するのであれば Shout
モジュールなどの名前にして、掛け声の部分だけ前に付け加えるというようなメソッドを定義する方が良いと思います。
終わりに
モジュールとクラスの継承階層についてのお話でした。
私は、これを初めて知った時にモジュールの理解が深まりました。
同じように感じてくれる方が一人でもいると嬉しいです。