使用している Ruby のバージョンは2.5.3
。
モジュールの定義
以下の構文で定義する。
module モジュール名 end
モジュールはクラスと違い、インスタンスを作ることは出来ない。
module MyModule end MyModule.new # undefined method `new' for MyModule:Module (NoMethodError)
ミックスイン
クラスが、あるモジュールを取り入れてそのモジュールのメソッドを使えるようにすることを、ミックスインと呼ぶ。
include
でミックスインすると、モジュールで定義したメソッドをインスタンスメソッドとして使えるようになる。
ミックスインしたクラスのサブクラスでも、そのまま使える。
module SelfIntroduction def who_am_i(oneself) p "I'm #{oneself}." end end class Parent include SelfIntroduction def hello print 'Hello! ' who_am_i self end end class Child < Parent def hey print 'Hey! ' who_am_i self end end parent = Parent.new parent.hello # Hello! "I'm #<Parent:0x00007ff97d895868>." child = Child.new child.hey # Hey! "I'm #<Child:0x00007ff97d895638>."
extend
を使ってミックスインすると、モジュールのメソッドを、そのクラスの特異メソッド(クラスメソッド)として使うことが出来る。
module SelfIntroduction def who_am_i(oneself) p "I'm #{oneself}." end end class User extend SelfIntroduction end User.who_am_i(User) # "I'm User."
include されているモジュールを調べる
include?
メソッドの引数にモジュールを渡すと、そのモジュールをinclude
しているかどうかが返ってくる。
included_modules
メソッドは、include
しているモジュールを配列で返す。
module SelfIntroduction end class Parent include SelfIntroduction end class Child < Parent end p Parent.include?(SelfIntroduction) # true p Child.include?(SelfIntroduction) # true p Parent.included_modules # [SelfIntroduction, Kernel] p Child.included_modules # [SelfIntroduction, Kernel]
Kernel とトップレベル
p
やrequire
といったメソッドは、Kernel
というモジュールで定義されている。
そして、Object
クラスがKernel
をinclude
しているため、Object
を継承している全てのクラスで、p
などのメソッドを使える。
p Object.included_modules # [Kernel]
Ruby では、クラス構文やモジュール構文などで囲まれていない一番外側の部分を、「トップレベル」と呼ぶ。
そしてトップレベルのself
は、main
という名前のオブジェクトを指すが、これはObject
クラスのインスタンスである。トップレベルでp
などを使えるのは、このため。
p self # main p self.class # Object p self.class.include?(Kernel) # true
モジュールは Module クラスのインスタンス
モジュールは、Module
クラスのインスタンスである。
module SelfIntroduction end p SelfIntroduction.class # Module
そして、Module
クラスは、Object
クラスを継承している。
p Module.superclass # Object
Class
クラスはModule
クラスを継承している。
そのため、継承関係はClass -> Module -> Object -> BasicObject
になっている。
p Class.superclass # Module p Module.superclass # Object p Object.superclass # BasicObject
全てのクラスは、Class
クラスのインスタンスでもある。
p Class.class # Class p Module.class # Class p Object.class # Class p BasicObject.class # Class
ここらへんは自分でも理解できていない。循環参照のようになっていないか? Class
はBasicObject
やObject
を継承しているが、それらスーパークラスは、Class
のインスタンスなのだから、Class
から生まれてくる。だからまず先にClass
が存在しているはず。しかしそのClass
はBasicObject
などを継承しておかないといけない。Class
定義時にはまだBasicObject
は存在しないにも拘わらず!
以下の状況が成立してしまうのがよく分からない。
p Module.instance_of?(Class) # true p Class.superclass # Module
名前空間としてモジュールを使う
クラス定義をモジュール構文で囲うことで、名前空間を分けることが出来る。
参照する際はモジュール名::クラス名
と記述する。
module Seller class Alice def role p 'I am seller.' end end end module Buyer class Alice def role p 'I am buyer.' end end end seller = Seller::Alice.new seller.role # "I am seller." buyer = Buyer::Alice.new buyer.role # "I am buyer."
モジュールを入れ子にすることも出来る。
module User module PremiumUser class Alice def initialize p 'This is premium user.' end end end end User::PremiumUser::Alice.new # "This is premium user."
モジュールが既に定義済みの場合、class モジュール名::クラス名
という構文でクラスを定義することも可能。
module Seller end class Seller::Alice def role p 'I am seller.' end end seller = Seller::Alice.new seller.role # "I am seller."
トップレベルで定義しているクラスを明示的に呼び出す場合は、::クラス名
と記述する。
class Alice end module Seller class Alice def initialize print Alice print ', ' p ::Alice end end end Seller::Alice.new # Seller::Alice, Alice
関数や定数を提供するためにモジュールを使う
モジュールに特異メソッドを定義すれば、ミックスインすることなくそのメソッドを使える。
module SelfIntroduction def self.who_am_i(oneself) p "I'm #{oneself}." end end SelfIntroduction.who_am_i 1 # "I'm 1."
ミックスインとしても特異メソッドとしても使えるメソッドを、モジュール関数という。
モジュール関数はmodule_function
を使って定義する。
モジュール関数は自動的にprivate
になるので、レシーバを指定して呼び出すことは出来ない。
module SelfIntroduction def who_am_i(oneself) p "I'm #{oneself}." end module_function :who_am_i end class User include SelfIntroduction def hello print 'Hello! ' who_am_i self end end SelfIntroduction.who_am_i 1 # "I'm 1." user = User.new user.hello # Hello! "I'm #<User:0x00007fed11115930>." user.who_am_i # private method `who_am_i' called for #<User:0x00007fed11115930> (NoMethodError)
モジュールは定数を定義することもでき、モジュール名::定数名
で取得できる。
module SelfIntroduction SOME_VALUE = 'foo' end p SelfIntroduction::SOME_VALUE # "foo"
メソッド探索
クラスに対してancestors
メソッドを使うと、クラスやモジュールの配列が返ってくる。
クラスのインスタンスがメソッドを呼び出すとき、ancestors
の返り値の順番でメソッドを探索し、最初に見つかったメソッドを実行する。
最後まで見つからなかった場合はNoMethodError
になる。
module A end class Parent include A def foo p "Parent's foo" end def bar p "Parent's bar" end end class Child < Parent def foo p "Child's foo" end end p Child.ancestors # [Child, Parent, A, Object, Kernel, BasicObject] child = Child.new child.foo # "Child's foo" child.bar # "Parent's bar"
モジュールをinclude
した場合、モジュールのメソッドより先にクラスのインスタンスメソッドを探索するが、prepend
メソッドでミックスインした場合は、ミックスインしたモジュールのメソッドを先に探索する。
module A end class Foo include A end class Bar prepend A end p Foo.ancestors # [Foo, A, Object, Kernel, BasicObject] p Bar.ancestors # [A, Bar, Object, Kernel, BasicObject]