Es ist bekannt, dass in Ruby Klassenmethoden vererbt werden:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
Es überrascht mich jedoch, dass es mit Mixins nicht funktioniert:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
Ich weiß, dass die Methode #extend dies kann:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
Aber ich schreibe ein Mixin (oder möchte lieber schreiben), das sowohl Instanzmethoden als auch Klassenmethoden enthält:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Was ich jetzt tun möchte, ist Folgendes:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
Ich möchte, dass A, B sowohl Instanz- als auch Klassenmethoden vom Common
Modul erben . Aber das funktioniert natürlich nicht. Gibt es nicht einen geheimen Weg, um diese Vererbung von einem einzigen Modul aus funktionieren zu lassen?
Es erscheint mir unelegant, dies in zwei verschiedene Module aufzuteilen, eines zum Einschließen, das andere zum Erweitern. Eine andere mögliche Lösung wäre die Verwendung einer Klasse Common
anstelle eines Moduls. Dies ist jedoch nur eine Problemumgehung. (Was ist, wenn es zwei Sätze gemeinsamer Funktionen gibt Common1
und Common2
wir wirklich Mixins benötigen?) Gibt es einen tiefen Grund, warum die Vererbung von Klassenmethoden von Mixins nicht funktioniert?
Antworten:
Eine gängige Redewendung ist die Verwendung von
included
Hook- und Inject-Klassenmethoden von dort.quelle
include
fügt Instanzmethoden hinzu,extend
fügt Klassenmethoden hinzu. So funktioniert es. Ich sehe keine Inkonsistenz, nur unerfüllte Erwartungen :)included
Methodendefinition in ein anderes Modul verschieben und DAS in Ihr Hauptmodul aufnehmen;)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
ancestors
Methode aufrufen :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 wirM
in aufgenommen habenC
,M
ist jetzt ein Vorfahr vonC
. Wenn wir alsofoo
eine Instanz von aufrufenC
, findet Ruby diese Methode inM
: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:
Wie implementiert Ruby Klassenmethoden?
In Ruby sind Klassen und Module einfache Objekte - sie sind Instanzen der Klasse
Class
undModule
. Dies bedeutet, dass Sie dynamisch neue Klassen erstellen, sie Variablen zuweisen können usw.: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 :
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:
Die üblichere Methode zum Definieren einer Klassenmethode ist die Verwendung
self
innerhalb des Klassendefinitionsblocks, der sich auf das zu erstellende Klassenobjekt bezieht: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!
Diese
self.singleton_class.include M::ClassMethods
Zeile sieht nicht sehr gut aus, also fügte Ruby hinzuObject#extend
, was dasselbe tut - dh ein Modul in die Singleton-Klasse des Objekts einschließt:Bewegen des
extend
Anrufs in das ModulDieses vorherige Beispiel ist aus zwei Gründen kein gut strukturierter Code:
include
undextend
in derHostClass
Definition aufrufen , damit unser Modul richtig aufgenommen wird. Dies kann sehr umständlich werden, wenn Sie viele ähnliche Module einbinden müssen.HostClass
Verweise direktM::ClassMethods
, dies ist ein Implementierungsdetail des ModulsM
,HostClass
das nicht bekannt sein oder sich darum kümmern sollte.Wie wäre es damit: Wenn wir
include
in der ersten Zeile aufrufen , benachrichtigen wir das Modul irgendwie darüber, dass es enthalten ist, und geben ihm auch unser Klassenobjekt, damit es sichextend
selbst 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.included
Methode 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: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:quelle
Wie Sergio in den Kommentaren erwähnt hat, ist es für Leute, die bereits in Rails sind (oder nichts dagegen haben, abhängig vom aktiven Support ),
Concern
hier hilfreich:quelle
Sie können Ihren Kuchen haben und ihn auch essen, indem Sie dies tun:
Wenn Sie beabsichtigen, Instanz- und Klassenvariablen hinzuzufügen, werden Sie sich am Ende die Haare ausreißen, da Sie auf eine Menge fehlerhaften Codes stoßen, es sei denn, Sie tun dies auf diese Weise.
quelle