Hier ist die ganze Geschichte, in der die notwendigen Metaprogrammierungskonzepte erläutert werden, die erforderlich sind, um zu verstehen, warum die Moduleinbeziehung so funktioniert wie in Ruby.
Was passiert, wenn ein Modul enthalten ist?
Durch das Einfügen eines Moduls in eine Klasse wird das Modul den Vorfahren der Klasse hinzugefügt. Sie können die Vorfahren jeder Klasse oder jedes Moduls anzeigen, indem Sie die folgende ancestorsMethode aufrufen :
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!
Wenn Sie eine Methode für eine Instanz von aufrufen C, überprüft Ruby jedes Element dieser Ahnenliste, um eine Instanzmethode mit dem angegebenen Namen zu finden. Da wir Min aufgenommen haben C, Mist jetzt ein Vorfahr von C. Wenn wir also fooeine Instanz von aufrufen C, findet Ruby diese Methode in M:
C.new.foo
#=> "foo"
Beachten Sie, dass durch die Aufnahme keine Instanz- oder Klassenmethoden in die Klasse kopiert werden. Sie fügt der Klasse lediglich einen "Hinweis" hinzu, nach dem auch Instanzmethoden im enthaltenen Modul gesucht werden sollen.
Was ist mit den "Klassen" -Methoden in unserem Modul?
Da durch die Aufnahme nur die Art und Weise geändert wird, in der Instanzmethoden versendet werden, werden durch das Einschließen eines Moduls in eine Klasse nur die Instanzmethoden für diese Klasse verfügbar . Die "Klassen" -Methoden und andere Deklarationen im Modul werden nicht automatisch in die Klasse kopiert:
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
Wie implementiert Ruby Klassenmethoden?
In Ruby sind Klassen und Module einfache Objekte - sie sind Instanzen der Klasse Classund Module. Dies bedeutet, dass Sie dynamisch neue Klassen erstellen, sie Variablen zuweisen können usw.:
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
Auch in Ruby haben Sie die Möglichkeit, sogenannte Singleton-Methoden für Objekte zu definieren. Diese Methoden werden als neue Instanzmethoden zur speziellen, versteckten Singleton-Klasse des Objekts hinzugefügt :
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]
Aber sind Klassen und Module nicht auch nur einfache Objekte? In der Tat sind sie! Bedeutet das, dass sie auch Singleton-Methoden haben können? Ja tut es! Und so entstehen Klassenmethoden:
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Die üblichere Methode zum Definieren einer Klassenmethode ist die Verwendung selfinnerhalb des Klassendefinitionsblocks, der sich auf das zu erstellende Klassenobjekt bezieht:
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Wie füge ich die Klassenmethoden in ein Modul ein?
Wie wir gerade festgestellt haben, sind Klassenmethoden wirklich nur Instanzmethoden für die Singleton-Klasse des Klassenobjekts. Bedeutet dies, dass wir nur ein Modul in die Singleton-Klasse aufnehmen können , um eine Reihe von Klassenmethoden hinzuzufügen? Ja tut es!
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"
Diese self.singleton_class.include M::ClassMethodsZeile sieht nicht sehr gut aus, also fügte Ruby hinzu Object#extend, was dasselbe tut - dh ein Modul in die Singleton-Klasse des Objekts einschließt:
class HostKlass
include M
extend M::ClassMethods
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ there it is!
Bewegen des extendAnrufs in das Modul
Dieses vorherige Beispiel ist aus zwei Gründen kein gut strukturierter Code:
- Wir müssen jetzt beide
include und extendin der HostClassDefinition aufrufen , damit unser Modul richtig aufgenommen wird. Dies kann sehr umständlich werden, wenn Sie viele ähnliche Module einbinden müssen.
HostClassVerweise direkt M::ClassMethods, dies ist ein Implementierungsdetail des Moduls M, HostClassdas nicht bekannt sein oder sich darum kümmern sollte.
Wie wäre es damit: Wenn wir includein der ersten Zeile aufrufen , benachrichtigen wir das Modul irgendwie darüber, dass es enthalten ist, und geben ihm auch unser Klassenobjekt, damit es sich extendselbst aufrufen kann. Auf diese Weise ist es Aufgabe des Moduls, die Klassenmethoden hinzuzufügen, wenn dies gewünscht wird.
Genau dafür ist die spezielle self.includedMethode gedacht. Ruby ruft diese Methode automatisch auf, wenn das Modul in einer anderen Klasse (oder einem anderen Modul) enthalten ist, und übergibt das Hostklassenobjekt als erstes Argument:
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!
Natürlich ist das Hinzufügen von Klassenmethoden nicht das einzige, was wir tun können self.included. Wir haben das Klassenobjekt, also können wir jede andere (Klassen-) Methode darauf aufrufen:
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end