Rubyのモジュールについて調べたのでまとめます。
動作確認はRuby 2.4.2でやってます。
Rubyにおけるモジュールについて
モジュールの特徴
- インスタンスを生成ができない
- 継承ができない
モジュールの使われ方
-
名前空間として利用する
-
Mixin
- include
-> モジュールのメソッドをクラスのインスタンスメソッドとして継承ツリーの上位に取り込む - prepend
-> モジュールのメソッドをクラスのインスタンスメソッドとして継承ツリーの下位に取り込む - extend
-> モジュールのメソッドをオブジェクトのクラスメソッドとして取り込む
- include
-
モジュール関数として利用する
定義
module Foo
end
名前空間として利用する
モジュールの定義の中にネストした別のモジュールやクラスを定義することで名前空間として利用できます。こうすることで同じ名前のクラス・メソッドなどを名前空間で分別することができます。名前空間のモジュールやクラスへのアクセスは::
を使用します。
module MotorCycle
module Onroad
class MotoGP
def foo(bar)
p bar
end
end
end
module Offroad
class MotocrossGP
def foo(bar)
p "#{bar} MotocrossGP_class!!!"
end
end
end
end
MotorCycle::Onroad::MotoGP # => MotorCycle::Onroad::MotoGP
mx_gp = MotorCycle::Offroad::MotocrossGP.new
mx_gp.foo('hoge') # => "hoge MotocrossGP_class!!!"
Mixin
ざっくりとクラスとモジュールの違いとしては、以下の通りで、同じ意図を持ったメソッドをモジュールにまとめて見通しを良くしたりするのに使われます。
クラス:インスタンス化できる。メソッドを定義できる。
モジュール:メソッドを定義できるが、インスタンス化できない。
そして、そのモジュールをクラスに取り込むことをMixinと言います。
Rubyの継承は1つのスーパークラスから受け継ぐ、単一継承のみ許可されており、多重継承が許可されておりません。しかし、クラスにモジュールを取り込む(Mixin)ことで多重継承のようなものを実現可能とします。ここではクラスにモジュールを取り込む方法として以下の3つの方法を説明します。
- include
- prepend
- extend
include
モジュールに定義されたメソッドをクラスにインスタンスメソッドとして取り込みます。モジュールをクラスに取り込むにはinclude
を使用します。
インスタンスメソッドとして取り込むので、メソッドのレシーバはインスタンスです。
module Foo
def foo_method
p 'foo!!!'
bar_method(self.class) # ここでのselfはこのモジュールを取り込んだクラスのインスタンスを返す
end
end
class Bar
include Foo
def bar_method(val)
p val
end
end
bar = Bar.new
bar.foo_method # => "foo!!!"
# => Bar
継承ツリー
include
を使用すると、そのクラスとスーパークラスの間にinclude
されたモジュールが内部的に定義されます。
以下の通り、継承ツリーはBar -> Foo(をラップしたクラス) -> SuperBar
となるため、メソッドの探索もBarから順に探索し、最初に見つかったメソッドが実行されます。
module Foo
end
class SuperBar
end
class Bar < SuperBar
include Foo
end
p Bar.ancestors # => [Bar, Foo, SuperBar, Object, Kernel, BasicObject]
####モジュールから取り込んだメソッドのオーバーライド
モジュールから取り込んだメソッドをオーバーライドするには以下のように行います。
module Foo
def foo_method
p 'foo_method!!!'
end
end
class Bar
include Foo
def foo_method
p 'bar_class!!!'
super # モジュール側に定義されたメソッドが呼び出される
end
end
bar = Bar.new
bar.foo_method # => "bar_class!!!"
# => "foo_method!!!"
prepend (ruby2.0~)
モジュールのメソッドをクラスのインスタンスメソッドとして継承ツリーの下位に取り込みます。前述のinclude
との違いは継承ツリーの下位に取り込まれるため、こちらの方がメソッドの実行順として優先されることです。
module Foo
def foo_method
p 'foo_module!!!'
super
end
end
class Bar
prepend Foo
def foo_method
p 'bar_class!!!'
end
end
bar = Bar.new
bar.foo_method # => "foo_module!!!"
# => "bar_class!!!"
よって、継承ツリーはinclude
と異なり、Foo(をラップしたクラス) => Bar => SuperBar
となります。
module Foo
end
class SuperBar
end
class Bar < SuperBar
prepend Foo
end
p Bar.ancestors # => [Foo, Bar, SuperBar, Object, Kernel, BasicObject]
extend
モジュールをオブジェクトの特異クラスとして取り込み、モジュールのメソッドを特異メソッドとして使用することができます。
オブジェクト(クラス)のクラスメソッドとして取り込む
以下のようにクラス内でextend
するとモジュールのメソッドをオブジェクトのクラスメソッドとして取り取り込むことができます。クラスメソッドなのでインスタンスからは呼び出せず、クラスをレシーバとしてクラスから直接呼び出します。
module Foo
def foo_method
p 'foo_method!!!'
end
end
class Bar
extend Foo
end
Bar.foo_method # => "foo_method!!!"
オブジェクト(インスタンス)の特異メソッドとして取り込む
以下のようにObject
のインスタンスにextend
すると、そのインスタンスにモジュールのメソッドを取り込むことができます。
module Foo
def foo_method
p 'foo_method!!!'
end
end
obj = Object.new
obj.extend Foo
obj.foo_method # => "foo_method!!!"
# 特異クラスの継承ツリーを確認するとFooが追加されている
p obj.singleton_class.ancestors # => [#<Class:#<Object:0x00007f7e55aed190>>, Foo, Object, Kernel, BasicObject]
モジュール関数として利用する
module_function
を使用するとモジュール関数として利用することができます。
モジュール関数とは、プライベートメソッドであると同時に モジュールの特異メソッドでもあるようなメソッドです。
module Foo
def foo_method
p 'foo_method!!!'
end
module_function :foo_method
end
# モジュールから直接呼び出す方法(モジュールの特異メソッドとして呼んでいる)
Foo.foo_method # => "foo_method!!!"
# includeして直接呼び出す方法
include Foo
foo_method # => "foo_method!!!"
モジュール関数を複数定義する場合は以下のようにmodule_function
以下に続けてメソッドを定義します。
module Foo
module_function
def foo_method_a
end
def foo_method_b
end
end
参考
『パーフェクトRuby』