すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Ruby

【Ruby】 クラスにモジュールをincludeする方法を図解形式で理解する!

ぴっかちゃん
ぴっかちゃん

includeにモジュールを指定する事で、そのモジュールのメソッドや定数を引き継ぐ事が出来ます。これは多重継承の代わりに使われるMix-inとも呼ばれます。

クラスにinclude モジュール名と記述することで、そのモジュールのインスタンスメソッドをクラスで使用出来るようになります。

includeの基本構文 -->
1
2
3
class クラス名
  include モジュール名
end

Greetモジュールに定義しているsay_helloメソッドObjクラスで使用する場合は、以下のようにObjクラスinclude Greetと記述します。

greet.rb | Greetモジュールを定義する -->
1
2
3
4
5
module Greet
  def say_hello  # このメソッドをObjクラスで使用したい
    "Hello!"
  end
end
obj.rb | includeの使用例 -->
1
2
3
4
5
6
7
8
9
10
11
12
require './greet' # Greetモジュールのファイルを読み込む

class Obj
  include Greet # Greetモジュールのメソッドを引き継ぐ
end


obj = Obj.new   # Objクラスのインスタンス生成
#=> #<Obj:0x007f9c771b33f0> 

puts obj.say_hello  # say_helloメソッドを呼ぶ
#=> Hello!

それでは、includeの使い方についてみていきましょう。

多重継承の実現(Mix-in)

この章では、多重継承を実現することが出来るincludeの基礎から応用までの使い方について詳しく解説します。

includeの基本的な使い方

先ほどのGreetモジュールObjクラスのサンプルコードを詳しく解説します。

greet.rb | Greetモジュールを定義する -->
1
2
3
4
5
module Greet
  def say_hello  # このメソッドをObjクラスで使用したい
    "Hello!"
  end
end

上記のGreetモジュールのsay_helloメソッドをObjクラスで使う場合は、以下のinclude GreetをObjクラスに記述すれば、使用することが出来ましたね。

obj.rb | say_helloメソッドをインスタンスメソッドとして使用した場合 -->
1
2
3
4
5
6
7
8
9
10
11
require './greet' # Greetモジュールのファイルを読み込む

class Obj
  include Greet  # Greetモジュールのメソッドを引き継ぐ
end

obj = Obj.new   # Objクラスのインスタンス生成
#=> #<Obj:0x007f9c771b33f0> 

puts obj.say_hello  # say_helloメソッドを呼ぶ
#=> Hello!

そして、includeしたsay_helloメソッドは、上記のobj.say_helloのようにObjクラスのインスタンスメソッドとして使用します。

ポイント

クラスの中でモジュールをincludeすると「モジュールのメソッドをクラスのインスタンスメソッド(obj.say_hello)」として使用する事が出来ます。

ここまでの流れは以下の通りです。

モジュールをincludeする流れ

クラスのクラスメソッド

includeしたGreetモジュールのsay_helloメソッドは、Objクラスのインスタンスメソッドとして呼び出すことは出来ますが、Objクラスのクラスメソッドとして呼び出す事が出来ないので注意してください!

以下のobj.say_helloは、Objクラスのインスタンスメソッドとして呼び出すことが出来ているのでHello!が返ります。

obj.rb | Objクラスのインスタンスメソッドとして呼び出すことが出来る -->
1
2
3
4
5
6
7
8
9
10
11
require './greet' # Greetモジュールのファイルを読み込む

class Obj
  include Greet  # Greetモジュールのメソッドを引き継ぐ
end

obj = Obj.new   # Objクラスのインスタンス生成
#=> #<Obj:0x007f9c771b33f0> 

puts obj.say_hello  # say_helloメソッドを呼ぶ
#=> Hello!

しかし、以下のObj.say_helloは、say_helloメソッドをObjクラスのクラスメソッドとして呼び出そうとしていますが、undefined methodのエラーが発生し呼び出すことが出来ません。

obj.rb |Objクラスのクラスメソッドとして呼び出すことが出来ない -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require './greet'

class Obj
  include Greet
end


obj = Obj.new   # Objクラスのインスタンス生成
#=> #<Obj:0x007f9c771b33f0> 

puts obj.say_hello  # インスタンスメソッドとして呼ぶと成功する
#=> Hello!

puts Obj.say_hello # クラスメソッドとして呼び出せない
# => undefined method `say_hello' for Obj:Class (NoMethodError)

このように、includeしたモジュールのメソッドは、そのクラスのクラスメソッドではなくインスタンスメソッドとして呼び出すことが出来ます。

インスタンスメソッドやクラスメソッドの違いについては、オブジェクト指向におけるクラスの概念を参考にしてください。

モジュールのクラスメソッド

クラスにモジュールをincludeしても、モジュールで定義したクラスメソッドは呼び出すことは出来ません。includeして呼び出せるのは、モジュールで定義しているインスタンスメソッドです。

例えば、Greetモジュールにsay_helloインスタンスメソッドだけではなく、以下のようにクラスメソッドのsay_goodbyeメソッドを追加します。

greet.rb | Greetモジュールにクラスメソッドを追加する -->
1
2
3
4
5
6
7
8
9
module Greet
  def self.say_goodbye # クラスメソッドを追加
    "Goodbye!"
  end

  def say_hello # インスタンスメソッド
    "Hello!"
  end
end

Greetモジュールのインスタンスメソッドであるsay_helloメソッドは、以下のようにincludeしてObjクラスのインスタンスメソッドとして呼び出すことが出来ています。

obj.rb | say_helloメソッドを呼び出した場合 -->
1
2
3
4
5
6
7
8
9
10
11
12
require './greet' 

class Obj
  include Greet
end


obj = Obj.new   # Objクラスのインスタンス生成
#=> #<Obj:0x007f9c771b33f0>

puts obj.say_hello  # say_helloメソッドを呼ぶ
#=> Hello!

しかし、Greetモジュールのクラスメソッドであるsay_goodbyeメソッドは、以下のようにincludeしてもObjクラスのインスタンスメソッドとして呼び出せず、エラーが発生します。

obj.rb | say_goodbyeメソッドを呼び出した場合 -->
1
2
3
4
5
6
7
8
require './greet' 

class Obj
  include Greet  
end

obj = Obj.new
puts obj.say_goodbye # => undefined method `say_goodbye'

上記のように、クラスで呼び出す事が出来るモジュールのメソッドは、インスタンスメソッドです。モジュールに定義されたクラスメソッドは呼び出すことが出来ませんので注意してください。

押さえるべき3つのポイント

基本的な使い方とincludeする際の注意点を解説してきましたが、一度整理しましょう。
ここまで押さえておくべきポイントは、以下のように3つあります。

モジュールをincludeする時の3つのポイントとは?
  1. モジュールのインスタンスメソッドは、クラスのインスタンスメソッドとして呼び出せる
  2. モジュールのインスタンスメソッドは、includeしたクラスのクラスメソッドとして呼び出せない
  3. モジュールのクラスメソッドは、includeしてもクラスのインスタンスメソッドとして呼び出せない

上記の1と2については、モジュールのインスタンスメソッドをincludeしたクラスで呼び出す場合について言及しています。

以下のように、Greetモジュールのインスタンスメソッド(say_helloメソッド)は、「①Objクラスのインスタンスメソッドとして呼び出せる」が、「②Objクラスのクラスメソッドとして呼び出せない」です。

Objクラスでモジュールをincludeする場合

そして、「3. モジュールのクラスメソッドは、includeしてもクラスのインスタンスメソッドとして呼び出せない」は、モジュールのクラスメソッドをincludeしたクラスで呼び出した場合について言及しています。

以下のように、GreetモジュールをincludeしたObjクラスでGreetモジュールのsay_goodbyeクラスメソッドを呼び出そうとすると、エラーが発生します。

Greetモジュールのクラスメソッドを呼び出す場合にエラーが発生する例

つまり、クラスにモジュールをincludeした場合は、クラスで呼び出せるのはモジュールのインスタンスメソッドで、includeしたクラスのインスタンスメソッドとして呼び出すことが出来ます。

includeしたクラスの
インスタンスメソッドで呼ぶ
includeしたクラスの
クラスメソッドで呼ぶ
モジュールの
インスタンスメソッド
呼び出せる 呼び出せない
モジュールの
クラスメソッド
呼び出せない 呼び出せない

クラスメソッドも同時に追加する場合

includeしたモジュールのインスタンスメソッドをクラスのインスタンスメソッドだけではなく、クラスメソッドとして使いたい場合にincludedが便利です。

例えば、以下のようにModモジュールで定義するrunメソッドは、Carクラスのクラスメソッドとして、stopメソッドはCarクラスのインスタンスメソッドとして使用したい場合があるとします。

mod.rb | Modモジュールにrun/stopインスタンスメソッドを定義-->
1
2
3
4
5
6
7
8
9
module Mod
  def run #クラスメソッドとして使いたい
    puts "GO!!"
  end

  def stop # インスタンスメソッドとして使いたい
    puts "STOP!!"
  end
end

このような場合は、以下のようにincludeとextendを使ってModモジュールを指定することで、上記の条件を満たします。

car.rb | クラスメソッドとしてもインスタンスメソッドとしても使いたい場合-->
1
2
3
4
5
6
7
8
9
10
11
require './mod'

class Car
  include Mod # インスタンスメソッドとして使えるように
  extend Mod # クラスメソッドとして使えるように

  car = Car.new
  car.stop #=> STOP!!

  Car.run #=> GO!!
end

しかし、以下のようにModモジュールにincludedを利用することで、Carクラスではincludeするだけでrunメソッドをクラスメソッドとして呼ぶことが出来ます。

mod.rb | includedを使う場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Mod
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def run #クラスメソッドとして使いたい
      puts "GO!!"
    end
  end

  def stop  # インスタンスメソッドとして使いたい
    puts "STOP!!"
  end
end
includeで指定したModモジュールがincludedを使っている場合-->
1
2
3
4
5
6
7
8
9
10
require './mod'

class Car
  include Mod

  car = Car.new
  car.stop #=> STOP!!

  Car.run #=> GO!!
end

このコードを理解するためにincludedについて解説します。

includedとは?

includedとは、クラスでモジュールがincludeされた際に呼び出されるメソッドです。引数にはincludeしたクラスが入ります。

例えば、CarクラスでModモジュールがincludeされた際に、以下のincludedが呼び出されます。baseの引数にはincludeしたCarクラスが入ります。

includedの仕組み-->
1
2
3
4
5
6
7
8
9
10
11
12
# mod.rb
module Mod
  def self.included(base)
    p "#{base} include #{self}"
  end
end

# car.rb
class Car
  include Mod
end
# => ""Car include Mod""

参考:Ruby 2.7.0 リファレンスマニュアルより

先ほどのコードに戻りますが、クラスメソッドで使用したいモジュールのrunメソッドを以下のようにClassMethodsモジュールに定義します。

mod.rb | includedを使う場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Mod
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods # モジュールにクラスメソッドで使用するメソッドを定義
    def run
      puts "GO!!"
    end
  end

  def stop 
    puts "STOP!!"
  end
end

そして、CarクラスでModモジュールがincludeされた際は上記のincludedが呼び出されます。includedでは、extendを使ってClassMethodsに定義してあるrunメソッドをCarクラスに結びつけているので、以下のようにinclude Modの記述だけでrunメソッドをクラスメソッドとして呼び出すことが出来ているのです。

includeで指定したModモジュールがincludedを使っている場合-->
1
2
3
4
5
6
7
8
9
10
require './mod'

class Car
  include Mod

  car = Car.new
  car.stop #=> STOP!!

  Car.run #=> GO!!
end

このように、includedを使用することでクラスメソッドも同時に追加することが出来るので便利です。

ぴっかちゃん

クラスメソッドなどしっかりと学びたい方は、こちらの参考書で理解を深めることができます。

Railsでincludeを使う場合

Rubyでは、includeしたいモジュールファイルをrequireしていましたが、Railsでは自作したモジュールは、app/lib以下に置くことでrequireでファイルを読み込む必要なくinclude モジュール名でモジュールをincludeすることが出来ます。

詳しくは、Railsのincludeにて解説させて頂きます。

includeの仕組み

Rubyには、多重継承という複数のクラスを継承する仕組みはありません。
以下のように、子クラスが親クラスを1つ持つことが出来る単一継承をサポートします。

子クラスは親クラスを1つ持つことが出来る(単一継承) -->
1
2
class 子クラス < 親クラス
end

しかし、この単一継承だけでは複数のクラスで同じ処理が必要な場合に、適切に対応する事が出来ません。

クラスの継承

まず、前提としてクラスの継承は、is aの関係が成り立つか考えます。

以下のコードでは、TeacherクラスPersonクラスを継承していますが、「Teacher is a person(先生は人である)」や「Person is a teacher(人は先生である)」が成り立つのでクラスの継承が成立します。

sample.rb | TeacherクラスがPersonクラスを継承する-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person
  def foo
    puts "Foo!!"
  end
end

class Teacher < Person  # Personを継承する
end

person = Person.new
person.foo # => Foo!!

teacher = Teacher.new
teacher.foo # Personクラスを継承したのでfooメソッドが使える 
# =>Foo!!

そして、Personクラスを継承したのでTeacherクラスでもfooメソッドを使用することが出来ています。

しかし、以下のCarクラスの場合はどうなるでしょうか?

sample.rb | 新たにCarクラスを定義する-->
1
2
class Car
end

TeacherクラスのようにPersonクラスを継承してfooメソッドを使いたいところですが、クラス継承はお互いis aの関係である必要があります。

sample.rb | 間違ったクラス継承の例-->
1
2
class Car < Person # is aの関係ではないので、このクラス継承はありえない
end

上記の継承は、「Car is a Person(車は人である)」や「Person is a Car(人は車である)」が成り立たないので、クラス継承自体は出来るとしてもこの継承は間違った使い方になってしまいます。

そこで、多重継承の代わりとなるRuby独自の機能であるMix-inを使って解決します。

Mix-inとは?

RubyにおけるMix-inとは、クラスにModuleのメソッドや定数を引き継ぐことです。

先ほどのサンプルコードで言えば、Personクラスに定義していたfooメソッドTeacherクラスだけではなく、Carクラスでも使いたいので、以下のようにモジュールに処理を切り分けます。

mod.rb | Modモジュールを定義する-->
1
2
3
4
5
module Mod
  def foo
    puts "Foo!!"
  end
end

上記のModモジュールのfooメソッドは、以下のinclude Modの記述でCarクラスに引き継ぐことが出来ます。

car.rb | Modモジュールをincludeしてfooメソッドを使用する-->
1
2
3
4
5
6
7
8
9
require './mod.rb'

class Car
  include Mod
end

car = Car.new
car.foo # Carクラスのインスタンスメソッドとして使用することが出来る
# => Foo!!

Modモジュールに定義したfooメソッドは、Carクラスのインスタンスメソッドとして使用する事が出来ます。

しかし、include Modの記述によって、なぜ「ModモジュールのfooメソッドをCarクラスに引き継ぐ」ことが出来るのでしょうか?

継承について確認してみましょう。

クラスとモジュールの継承

クラスにモジュールをincludeした場合の継承について解説します。

まずクラスの継承ですが、以下のCarクラスのように親クラスを指定しないで定義した場合は、自動的に親クラスはObjectになります。

car.rb | Carクラスの定義-->
1
2
class Car
end

そして、Objectの親クラスはBasicObjectになります。親クラスは、以下のようにsuperclassメソッドで確認することが出来ます。

car.rb | Carクラスの親クラスを確認する -->
1
2
3
4
5
class Car
  puts Car.superclass      #=> Object
  puts Object.superclass      #=> BasicObject
  puts BasicObject.superclass #=> nil
end

つまり、Carクラスを定義した時点で、以下の画像のようなクラス継承が行われているのです。

Carクラスのクラス継承

そして、CarクラスにModモジュールをincludeすると、以下の画像のようにCarクラスと親クラスであるObjectとの継承の間にModモジュールが組み込まれます。

Modモジュールをincludeした時のクラスの継承関係

これは、メソッド探索の優先順位にも関係します。
メソッド探索の優先順位(順序)をancestorsメソッドで確認すると、以下のコードのような結果になります。

car.rb | メソッド探索の優先順位-->
1
2
3
4
class Car
  include Mod
  p ancestors  #=> [Car, Mod, Object, Kernel, BasicObject]
end

上記の[Car, Mod, Object, Kernel, BasicObject]は、左から順番にメソッド探索の優先順位が高くなります。

メソッド探索の優先順位

car.rb | Carクラスのインスタンスメソッドとしてfooメソッドを呼ぶ場合-->
1
2
3
4
5
6
class Car
  include Mod
end

car = Car.new
car.foo # Carクラスのインスタンスメソッドとしてfooメソッドを呼ぶ

例えば、上記のようにCarクラスのインスタンスメソッドとしてfooメソッドを呼ぶ場合は、以下の画像の順番でfooメソッドの探索をします。

fooメソッドを探索する際の優先順位

fooメソッドは、Modモジュールに定義されているので、メソッド探索はMod#fooで終わります。

メソッド探索の優先順位-->
1
2
3
4
5
Car#foo
Mod#foo → ここで定義されているので探索は終わる
Object#foo
Kernel#foo
Basic#Object

このメソッド探索の優先順位は重要です。
何故なら、クラスとモジュールに同名のメソッドが定義されていた場合に、クラスのメソッドが優先して呼ばれるからです。

例えば、CarクラスにModモジュールをincludeした場合は、以下のようにModモジュールfooメソッドが呼び出されていました。

mod.rb | Modモジュールを定義する-->
1
2
3
4
5
module Mod
  def foo
    puts "Foo!!"
  end
end
car.rb | Modモジュールに定義するfooメソッドをCarクラスのインスタンスメソッドとして呼ぶ-->
1
2
3
4
5
6
7
8
require './mod'

class Car
  include Mod
end

car = Car.new
car.foo  #=> Foo!!

しかし、以下のようにCarクラスfooメソッドを定義すると、Modモジュールのfooメソッドより先に優先されて呼び出されます。

car.rb | Modモジュールに定義してあるfooメソッドをCarクラスのインスタンスメソッドとして呼ぶ-->
1
2
3
4
5
6
7
8
9
10
11
require './mod'

class Car
  include Mod
  def foo # 同名のメソッドを定義
    puts "Carクラスに定義したFoo!!"
  end
end

car = Car.new
car.foo  #=> Carクラスに定義したFoo!!

このように、クラスにモジュールをincludeすると、そのクラスと親クラスの継承関係の間にモジュールが組み込まれて、メソッドを探索する際に自クラス、includeしたモジュール、親クラスの順番で探します。

includeによってモジュールが組み込まれた際の関係図

これは、以下のようにCarクラスVehicleクラスを継承させた場合でも同様の順序になります。

CarクラスにVehicleクラスを継承させた場合のfooメソッドの優先順位-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Module Mod
  def foo
    puts "これはModモジュールのFoo!!"
  end
end

class Vehicle
  def foo
    puts "これはCarの親クラスのFoo!!"
  end
end

class Car < Vehicle # 継承
  include Mod
end

car = Car.new
car.foo #=> これはModモジュールのFoo!!

親クラスをVehicleにした場合のメソッド探索の優先順位

クラスにモジュールをincludeした際は、そのクラスと親クラスの継承関係の間にモジュールが組み込まれるということを覚えておきましょう。

複数moduleをincludeする場合

クラスに複数のmoduleをincludeする場合は、そのクラスと親クラスの継承関係の間に複数のモジュールが組み込まれます。

クラスに複数のモジュールが組み込まれる場合

しかし、クラスに複数のモジュールをincludeする際は、以下の2つの方法によってメソッド探索の優先順位が異なるので注意が必要です。

car.rb | includeの引数にModuleを複数指定した場合-->
1
2
3
4
5
6
7
8
9
10
# 1. includeの引数にModuleを複数指定する
class Car
  include Mod, Mod2
end

# 2. include モジュール名を1行ずつ記述する
class Car
  include Mod
  include Mod2
end

それでは、以下のModモジュールMod2モジュールをincludeして確かめてみましょう。

モジュールファイル| 2つのModuleをincludeする場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
# mod.rb
module Mod
  def foo
    puts "Foo!!"
  end
end

# mod2.rb
module Mod2
  def bar
    puts "Bar!!"
  end
end

まずは、「 1. includeの引数にModuleを複数指定する」場合です。
メソッド探索の順序をancestorsメソッドで確認すると、以下のコードの結果になります。

car.rb | includeの引数にModuleを複数指定した場合-->
1
2
3
4
5
6
7
require './mod'
require './mod2'

class Car
  include Mod, Mod2
  p ancestors   #=> [Car, Mod, Mod2, Object, Kernel, BasicObject]
end

メソッド探索の順序は、Carクラスの後にincludeで指定した第一引数のModモジュールから順番に探索されます。

includeの引数に複数モジュールを渡した場合のメソッド探索順序

次に、「2. include モジュール名を1行ずつ記述する」場合です。
メソッド探索の順序をancestorsメソッドで確認すると、以下のコードの結果になります。

car.rb | includeの引数にModuleを複数指定した場合-->
1
2
3
4
5
6
7
8
require './mod'
require './mod2'

class Car
  include Mod
  include Mod2
  p ancestors   #=> [Car, Mod2, Mod, Object, Kernel, BasicObject]
end

メソッド探索の順序は、Carクラスの後に最後に定義したinclude モジュール名から順番に探索されます。

include モジュール名で1行ずつ記述した場合のメソッド探索順

複数のモジュールをincludeする際によく使われるのは、上記の1行1行記述する方法なので、最後に定義したモジュールがメソッド探索の優先順位が高くなるという事を覚えておきましょう。

extend、prependとの違い

RubyのMix-inは、includeの他にextendやRuby2.0から追加されたprependがありますが、この2つのメソッドはincludeとどのような違いがあるのでしょうか?

includeと比較しながら解説します。

extend

extendは、以下のように記述する事で指定したモジュールのインスタンスメソッドをextendしたクラスのクラスメソッドとして扱うことが出来ます。

extendの基本構文 -->
1
2
3
class クラス名
  extend モジュール名
end

それでは、以下のModモジュールをextendして確かめてみましょう。

mod.rb | Modモジュールを定義する-->
1
2
3
4
5
module Mod
  def foo
    puts "Foo!!"
  end
end

まずは、クラスにModモジュールをincludeすると、以下のようにModモジュールfooメソッド(インスタンスメソッド)をCarクラスインスタンスメソッドとして呼び出すことが出来ます。

car.rb | includeを使った場合 -->
1
2
3
4
5
6
7
8
9
require './mod'

class Car
  include Mod
end

car = Car.new
# Carクラスのインスタンスメソッドとしてfooメソッドを使用
car.foo  #=> Foo!!

そして、クラスにModモジュールをextendすると、以下のCar.fooのようにModモジュールのfooメソッド(インスタンスメソッド)をCarクラスクラスメソッドとして呼び出すことが出来ます。

car.rb | extendを使った場合 -->
1
2
3
4
5
6
7
8
require './mod'

class Car
  extend Mod
end

# Carクラスのクラスメソッドとしてfooメソッドを使用
Car.foo #=> Foo!!

また、includeと同様にモジュールに定義されたクラスメソッドは、extendしてもクラスのクラスメソッドとして使用する事が出来ませんので注意してください。

mod.rb | クラスメソッドを追加-->
1
2
3
4
5
6
7
8
9
module Mod
  def self.bar # クラスメソッド追加
    puts "Bar!!"
  end

  def foo
    puts "Foo!!"
  end
end

例えば、上記のModモジュールのクラスメソッドであるbarメソッドを以下のようにCarクラスのクラスメソッドとして使用した場合は、undefined methodのエラーが発生します。

car.rb | extendを使った場合 -->
1
2
3
4
5
6
7
8
9
require './mod'

class Car
  extend Mod
end

Car.foo #=> Foo!!

Car.bar #=> undefined method `bar' for Car:Class (NoMethodError)

そして、extendでモジュールのインスタンスメソッドクラスメソッドとして扱えるのは、extendで指定したモジュールのインスタンスメソッドが現在のオブジェクト(self)に結びつくメソッド(特異メソッド)として追加されるからです。(Carクラス自体も、Classクラスのオブジェクトです。)

includeとextendの違い

つまり、includeのようにクラスと親クラスの継承関係の間に組み込まれるのではなく、Modモジュールのインスタンスメソッドは、Carクラス(Classクラスのオブジェクト)に結びつくメソッドとして追加されるので、以下のancestorsメソッドで確認しても継承チェーンには組み込まれていません。

car.rb | anscestorsで継承チェーンを確認する-->
1
2
3
4
5
6
require './mod'

class Car
  extend Mod
    p ancestors #=> [Car, Object, Kernel, BasicObject]
end

extendは、モジュールのインスタンスメソッドをあるオブジェクトに結びつくメソッド(特異メソッド)として追加したい場合に使われます。

extendについて詳しくは、別記事にて解説させて頂きます。

prepend

prependは、includeと同様に指定したモジュールのインスタンスメソッドをprependしたクラスのインスタンスメソッドとして扱うことが出来ます。

prependの基本構文 -->
1
2
3
class クラス名
  prepend モジュール名
end
car.rb | prependの使用例 -->
1
2
3
4
5
6
7
8
9
require './mod'

class Car
  prepend Mod
end

car = Car.new
# Carクラスのインスタンスメソッドとしてfooメソッドを使用
car.foo #=> Foo!!

しかし、includeは自クラスと親クラスの継承の間に組み込まれましたが、prependで指定されたモジュールは自クラスの先頭に追加されます。

includeとprependの違い

メソッド探索の優先順位(順序)をancestorsメソッドで確認すると、以下のコードの結果のようにModモジュールがCarの先頭に追加されていることが分かります。

car.rb | includeを使った場合 -->
1
2
3
4
5
6
require './mod'

class Car
  prepend Mod
  p ancestors #=> [Mod, Car, Object, Kernel, BasicObject]
end

つまりクラスにモジュールをprependすると、クラスのインスタンスメソッドをモジュールで同名のメソッドを定義することによって、オーバーライド(上書き)することが出来るのです!

例えば、以下のようにCarクラスbarメソッドと同名のメソッドがModモジュールにあるとします。

mod.rb | Modモジュールを定義する-->
1
2
3
4
5
module Mod
  def bar # Carクラスと同名のメソッド
    puts "Modモジュールのbarメソッドが呼び出されました"
  end
end

そして、以下のcar.barのようにbarメソッドを呼び出すと、Modモジュールで定義したbarメソッドが呼び出されます。

car.rb | prependでModモジュールを継承チェーンの先頭に追加する -->
1
2
3
4
5
6
7
8
9
10
11
require './mod'

class Car
  prepend Mod
  def bar # Modモジュールと同名のメソッド
    puts "Carクラスのbarメソッドが呼び出されました"
  end
end

car = Car.new
car.bar #=> Modモジュールのbarメソッドが呼び出されました

また、モジュールのインスタンスメソッドでsuperを使用すると、クラスのインスタンスメソッド(モジュールのインスタンスメソッドと同名)にアクセスすることが出来ます。

mod.rb | superを使ってクラスのインスタンスメソッドにアクセスする-->
1
2
3
4
5
6
module Mod
  def bar
    puts "Modモジュールのbarメソッドが呼び出されました"
    super # Carクラスのbarメソッドを呼び出す
  end
end
car.rb | prependでModモジュールを継承チェーンの先頭に追加する -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
require './mod'

class Car
  prepend Mod

  def bar # Modモジュールと同名のメソッド
    puts "Carクラスのbarメソッドが呼び出されました"
  end
end

car = Car.new
car.bar 
#=> Modモジュールのbarメソッドが呼び出されました
#=> Carクラスのbarメソッドが呼び出されました

このように、prependでモジュールを指定するとクラスのインスタンスメソッドをオーバーライドすることが出来ます。

この記事のまとめ

  • クラスにモジュールをincludeすることで、モジュールのインスタンスメソッドをクラスのインスタンスメソッドとして呼ぶことが出来る
  • includeしたモジュールは、クラスと親クラスの継承の間に組み込まれる
  • includeしてクラスで呼び出せるのは、モジュールのインスタンスメソッドでクラスメソッドはないので注意しよう!