これが完全なストーリーであり、モジュールの組み込みがRubyで機能する理由を理解するために必要なメタプログラミングの概念を説明しています。
モジュールが含まれているとどうなりますか?
モジュールをクラスに含めると、そのモジュールがクラスの祖先に追加されます。ancestors
メソッドを呼び出すことで、任意のクラスまたはモジュールの祖先を確認できます。
module M
def foo; "foo"; end
end
class C
include M
def bar; "bar"; end
end
C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
# ^ look, it's right here!
のインスタンスでメソッドを呼び出すとC
、Rubyはこの祖先リストのすべての項目を調べて、指定された名前のインスタンスメソッドを見つけます。に含めM
たのでC
、M
はの祖先になりました。したがって、のインスタンスC
を呼び出すと、Rubyはそのメソッドを次の場所で見つけます。foo
C
M
C.new.foo
#=> "foo"
インクルードはインスタンスまたはクラスメソッドをクラスにコピーしないことに注意してください。インクルードされたモジュールでインスタンスメソッドも検索する必要があるという「メモ」をクラスに追加するだけです。
モジュールの「クラス」メソッドはどうですか?
インクルードはインスタンスメソッドのディスパッチ方法を変更するだけなので、モジュールをクラスにインクルードすると、そのインスタンスメソッドはそのクラスでのみ使用可能になります。モジュール内の「クラス」メソッドおよびその他の宣言は、クラスに自動的にコピーされません。
module M
def instance_method
"foo"
end
def self.class_method
"bar"
end
end
class C
include M
end
M.class_method
#=> "bar"
C.new.instance_method
#=> "foo"
C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class
Rubyはクラスメソッドをどのように実装しますか?
Rubyでは、クラスとモジュールはプレーンオブジェクトであり、クラスClass
とのインスタンスですModule
。これは、新しいクラスを動的に作成したり、変数に割り当てたりできることを意味します。
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
また、Rubyでは、オブジェクトに対していわゆるシングルトンメソッドを定義する可能性があります。これらのメソッドは、オブジェクトの特別な非表示のシングルトンクラスに新しいインスタンスメソッドとして追加されます。
obj = Object.new
# define singleton method
def obj.foo
"foo"
end
# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]
しかし、クラスとモジュールも単なるオブジェクトではありませんか?実際、彼らはそうです!それは彼らもシングルトンメソッドを持つことができるという意味ですか?はい、そうです!そして、これがクラスメソッドの誕生です。
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
または、クラスメソッドを定義するより一般的な方法は、self
作成中のクラスオブジェクトを参照するクラス定義ブロック内で使用することです。
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
モジュールにクラスメソッドを含めるにはどうすればよいですか?
確立したばかりのように、クラスメソッドは実際にはクラスオブジェクトのシングルトンクラスの単なるインスタンスメソッドです。これは、モジュールをシングルトンクラスに含めて、一連のクラスメソッドを追加できることを意味しますか?はい、そうです!
module M
def new_instance_method; "hi"; end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
self.singleton_class.include M::ClassMethods
end
HostKlass.new_class_method
#=> "hello"
このself.singleton_class.include M::ClassMethods
行はあまり見栄えが良くないので、Rubyが追加しましObject#extend
た。これは同じことを行います。つまり、オブジェクトのシングルトンクラスにモジュールが含まれています。
class HostKlass
include M
extend M::ClassMethods
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ there it is!
extend
通話をモジュールに移動する
この前の例は、次の2つの理由から、適切に構造化されたコードではありません。
- モジュールを適切に含めるには、定義でとの両方 を呼び出す必要があります。同様のモジュールを多数含める必要がある場合、これは非常に面倒になる可能性があります。
include
extend
HostClass
HostClass
を直接参照しますM::ClassMethods
。これは、モジュールの実装の詳細であり、知る必要も気にする必要もありませんM
。HostClass
では、これについてはどうでしょうか。include
最初の行を呼び出すときに、モジュールが含まれていることをモジュールに通知し、クラスオブジェクトを指定して、モジュールextend
自体を呼び出すことができるようにします。このように、必要に応じてクラスメソッドを追加するのはモジュールの仕事です。
これがまさに特別なself.included
方法です。Rubyは、モジュールが別のクラス(またはモジュール)に含まれるたびにこのメソッドを自動的に呼び出し、最初の引数としてホストクラスオブジェクトを渡します。
module M
def new_instance_method; "hi"; end
def self.included(base) # `base` is `HostClass` in our case
base.extend ClassMethods
end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
def self.existing_class_method; "cool"; end
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ still there!
もちろん、クラスメソッドを追加することだけがでできることではありませんself.included
。クラスオブジェクトがあるので、他の(クラス)メソッドを呼び出すことができます。
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end