KaQiita

新米社会人が適当なことを書いてます。温かく見守ってやってください。

Ruby におけるモジュールとクラスの継承階層

はじめに

「モジュールとクラス」というのは、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.

継承階層

これは、継承階層が以下のようになっているからです。

スクリーンショット 2018-10-07 23.23.25.png PerfectHuman クラス の上に OrientalRadio がいます。

このように描くと、少し混乱させてしまうかもしれません。

現に PerfectHuman.superclassHuman を返します。

puts PerfectHuman.superclass #=> Human

しかしこれは、特異クラスが無名で見えず、superclass メソッド、class メソッドでは出てこないためです。

特異クラスについてはこちらの記事を参照。

つまり、PerfectHuman クラスが OrientalRadio モジュールを include したことで、PerfectHuman クラスのすぐ上に OrientalRadio の特異クラスが作られたという訳です。

継承階層を正しく把握することのメリット

少し細かい話ですが、このような継承階層を正しく理解しておくことには、いくつかのメリットがあると考えています。

メソッドが自分の意図していない動作をすることを防ぐ

これは分かりやすくて、継承階層を理解していないと、自分が意図した通りに動作しない場合が起こり得るためです。

PerfectHuman クラスが Human クラスを継承していても、コード例のような場合には、Human クラスのメソッドが実行されないということがあります。

モジュールを正しく実装できる

ところで「継承階層」の図を観て、不自然に感じなかったでしょうか?

PerfectHuman の上に OrientalRadio がいるのは少しおかしな話ではありませんか?

階層構造を正しく理解していれば、OrientalRadio モジュールと Human クラス・PerfectHuman クラスの抽象度が一貫していないことに気付かれると思います。

つまり、OrientalRadio モジュールを後から定義するのは良い書き方とは言えず、後から定義するのであれば Shout モジュールなどの名前にして、掛け声の部分だけ前に付け加えるというようなメソッドを定義する方が良いと思います。

終わりに

モジュールとクラスの継承階層についてのお話でした。

私は、これを初めて知った時にモジュールの理解が深まりました。

同じように感じてくれる方が一人でもいると嬉しいです。

参考文献