これは完全なストーリーであり、モジュールのインクルードが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
ので、我々が呼ぶときfoo
のインスタンス上でC
、Rubyはにその方法を見つけるだろう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