更新日:
【Ruby】 クラスにモジュールをincludeする方法を図解形式で理解する!
includeにモジュールを指定する事で、そのモジュールのメソッドや定数を引き継ぐ事が出来ます。これは多重継承の代わりに使われるMix-inとも呼ばれます。
クラスにinclude モジュール名
と記述することで、そのモジュールのインスタンスメソッドをクラスで使用出来るようになります。
1
2
3
class クラス名
include モジュール名
end
Greetモジュールに定義しているsay_helloメソッド
をObjクラス
で使用する場合は、以下のようにObjクラス
にinclude Greet
と記述します。
1
2
3
4
5
module Greet
def say_hello # このメソッドをObjクラスで使用したい
"Hello!"
end
end
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クラス
のサンプルコードを詳しく解説します。
1
2
3
4
5
module Greet
def say_hello # このメソッドをObjクラスで使用したい
"Hello!"
end
end
上記のGreetモジュールのsay_helloメソッドをObjクラスで使う場合は、以下のinclude Greet
を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!
そして、includeしたsay_helloメソッドは、上記のobj.say_hello
のようにObjクラスのインスタンスメソッドとして使用します。
ここまでの流れは以下の通りです。
クラスのクラスメソッド
includeしたGreetモジュールのsay_helloメソッドは、Objクラスのインスタンスメソッドとして呼び出すことは出来ますが、Objクラスのクラスメソッドとして呼び出す事が出来ないので注意してください!
以下のobj.say_hello
は、Objクラスのインスタンスメソッドとして呼び出すことが出来ているので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!
しかし、以下のObj.say_hello
は、say_helloメソッドをObjクラスのクラスメソッドとして呼び出そうとしていますが、undefined method
のエラーが発生し呼び出すことが出来ません。
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メソッド
を追加します。
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クラスのインスタンスメソッドとして呼び出すことが出来ています。
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クラスのインスタンスメソッドとして呼び出せず、エラーが発生します。
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つあります。
上記の1と2については、モジュールのインスタンスメソッドをincludeしたクラスで呼び出す場合について言及しています。
以下のように、Greetモジュールのインスタンスメソッド(say_helloメソッド
)は、「①Objクラスのインスタンスメソッドとして呼び出せる」が、「②Objクラスのクラスメソッドとして呼び出せない」です。
そして、「3. モジュールのクラスメソッドは、includeしてもクラスのインスタンスメソッドとして呼び出せない」は、モジュールのクラスメソッドをincludeしたクラスで呼び出した場合について言及しています。
以下のように、GreetモジュールをincludeしたObjクラスでGreetモジュールのsay_goodbyeクラスメソッド
を呼び出そうとすると、エラーが発生します。
つまり、クラスにモジュールをincludeした場合は、クラスで呼び出せるのはモジュールのインスタンスメソッドで、includeしたクラスのインスタンスメソッドとして呼び出すことが出来ます。
includeしたクラスの インスタンスメソッドで呼ぶ |
includeしたクラスの クラスメソッドで呼ぶ |
|
---|---|---|
モジュールの インスタンスメソッド |
呼び出せる | 呼び出せない |
モジュールの クラスメソッド |
呼び出せない | 呼び出せない |
クラスメソッドも同時に追加する場合
includeしたモジュールのインスタンスメソッドをクラスのインスタンスメソッド
だけではなく、クラスメソッド
として使いたい場合にincluded
が便利です。
例えば、以下のようにModモジュールで定義するrunメソッド
は、Carクラスのクラスメソッド
として、stopメソッド
はCarクラスのインスタンスメソッド
として使用したい場合があるとします。
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モジュールを指定することで、上記の条件を満たします。
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メソッドをクラスメソッドとして呼ぶことが出来ます。
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
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クラスが入ります。
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""
先ほどのコードに戻りますが、クラスメソッドで使用したいモジュールのrunメソッド
を以下のようにClassMethods
モジュールに定義します。
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メソッドをクラスメソッドとして呼び出すことが出来ているのです。
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
2
class 子クラス < 親クラス
end
しかし、この単一継承だけでは複数のクラスで同じ処理が必要な場合に、適切に対応する事が出来ません。
クラスの継承
まず、前提としてクラスの継承は、is a
の関係が成り立つか考えます。
以下のコードでは、Teacherクラス
がPersonクラス
を継承していますが、「Teacher is a person(先生は人である)」や「Person is a teacher(人は先生である)」が成り立つのでクラスの継承が成立します。
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クラス
の場合はどうなるでしょうか?
1
2
class Car
end
Teacherクラス
のようにPersonクラス
を継承してfooメソッド
を使いたいところですが、クラス継承はお互いis a
の関係である必要があります。
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クラス
でも使いたいので、以下のようにモジュールに処理を切り分けます。
1
2
3
4
5
module Mod
def foo
puts "Foo!!"
end
end
上記のModモジュールのfooメソッドは、以下のinclude Mod
の記述でCarクラス
に引き継ぐことが出来ます。
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
になります。
1
2
class Car
end
そして、Object
の親クラスはBasicObject
になります。親クラスは、以下のようにsuperclassメソッド
で確認することが出来ます。
1
2
3
4
5
class Car
puts Car.superclass #=> Object
puts Object.superclass #=> BasicObject
puts BasicObject.superclass #=> nil
end
つまり、Carクラス
を定義した時点で、以下の画像のようなクラス継承が行われているのです。
そして、Carクラス
にModモジュールをincludeすると、以下の画像のようにCarクラス
と親クラスであるObject
との継承の間にModモジュールが組み込まれます。
これは、メソッド探索の優先順位にも関係します。
メソッド探索の優先順位(順序)をancestorsメソッド
で確認すると、以下のコードのような結果になります。
1
2
3
4
class Car
include Mod
p ancestors #=> [Car, Mod, Object, Kernel, BasicObject]
end
上記の[Car, Mod, Object, Kernel, BasicObject]
は、左から順番にメソッド探索の優先順位が高くなります。
1
2
3
4
5
6
class Car
include Mod
end
car = Car.new
car.foo # Carクラスのインスタンスメソッドとしてfooメソッドを呼ぶ
例えば、上記のようにCarクラスのインスタンスメソッドとして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メソッド
が呼び出されていました。
1
2
3
4
5
module Mod
def foo
puts "Foo!!"
end
end
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メソッドより先に優先されて呼び出されます。
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したモジュール、親クラスの順番で探します。
これは、以下のようにCarクラス
にVehicleクラス
を継承させた場合でも同様の順序になります。
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!!
クラスにモジュールをincludeした際は、そのクラスと親クラスの継承関係の間にモジュールが組み込まれるということを覚えておきましょう。
複数moduleをincludeする場合
クラスに複数のmoduleをincludeする場合は、そのクラスと親クラスの継承関係の間に複数のモジュールが組み込まれます。
しかし、クラスに複数のモジュールをincludeする際は、以下の2つの方法によってメソッド探索の優先順位が異なるので注意が必要です。
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して確かめてみましょう。
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メソッド
で確認すると、以下のコードの結果になります。
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モジュール
から順番に探索されます。
次に、「2. include モジュール名
を1行ずつ記述する」場合です。
メソッド探索の順序をancestorsメソッド
で確認すると、以下のコードの結果になります。
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行1行記述する方法なので、最後に定義したモジュールがメソッド探索の優先順位が高くなるという事を覚えておきましょう。
extend、prependとの違い
RubyのMix-inは、includeの他にextendやRuby2.0から追加されたprependがありますが、この2つのメソッドはincludeとどのような違いがあるのでしょうか?
includeと比較しながら解説します。
extend
extendは、以下のように記述する事で指定したモジュールのインスタンスメソッドをextendしたクラスのクラスメソッドとして扱うことが出来ます。
1
2
3
class クラス名
extend モジュール名
end
それでは、以下のModモジュール
をextendして確かめてみましょう。
1
2
3
4
5
module Mod
def foo
puts "Foo!!"
end
end
まずは、クラスにModモジュールをincludeすると、以下のようにModモジュール
のfooメソッド
(インスタンスメソッド)をCarクラス
のインスタンスメソッド
として呼び出すことが出来ます。
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クラス
のクラスメソッド
として呼び出すことが出来ます。
1
2
3
4
5
6
7
8
require './mod'
class Car
extend Mod
end
# Carクラスのクラスメソッドとしてfooメソッドを使用
Car.foo #=> Foo!!
また、includeと同様にモジュールに定義されたクラスメソッドは、extendしてもクラスのクラスメソッドとして使用する事が出来ませんので注意してください。
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
のエラーが発生します。
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のようにクラスと親クラスの継承関係の間に組み込まれるのではなく、Modモジュールのインスタンスメソッドは、Carクラス(Classクラスのオブジェクト)に結びつくメソッドとして追加されるので、以下のancestorsメソッド
で確認しても継承チェーンには組み込まれていません。
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したクラスのインスタンスメソッドとして扱うことが出来ます。
1
2
3
class クラス名
prepend モジュール名
end
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で指定されたモジュールは自クラスの先頭に追加されます。
メソッド探索の優先順位(順序)をancestorsメソッドで確認すると、以下のコードの結果のようにModモジュールがCarの先頭に追加されていることが分かります。
1
2
3
4
5
6
require './mod'
class Car
prepend Mod
p ancestors #=> [Mod, Car, Object, Kernel, BasicObject]
end
つまりクラスにモジュールをprependすると、クラスのインスタンスメソッドをモジュールで同名のメソッドを定義することによって、オーバーライド(上書き)することが出来るのです!
例えば、以下のようにCarクラス
のbarメソッド
と同名のメソッドがModモジュール
にあるとします。
1
2
3
4
5
module Mod
def bar # Carクラスと同名のメソッド
puts "Modモジュールのbarメソッドが呼び出されました"
end
end
そして、以下のcar.bar
のようにbarメソッドを呼び出すと、Modモジュール
で定義したbarメソッド
が呼び出されます。
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
を使用すると、クラスのインスタンスメソッド(モジュールのインスタンスメソッドと同名)にアクセスすることが出来ます。
1
2
3
4
5
6
module Mod
def bar
puts "Modモジュールのbarメソッドが呼び出されました"
super # Carクラスのbarメソッドを呼び出す
end
end
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してクラスで呼び出せるのは、モジュールのインスタンスメソッドでクラスメソッドはないので注意しよう!