Entwirren von Klassen, Instanzen und Metaklassen in Ruby

Willkommen zu einer neuen Episode von Ruby Magic! In diesem Monat dreht sich alles um Metaklassen, ein Thema, das durch eine Diskussion zwischen zwei Entwicklern (Hi Maud!).

Durch die Untersuchung von Metaklassen lernen wir, wie Klassen- und Instanzmethoden in Ruby funktionieren. Entdecken Sie dabei den Unterschied zwischen der Definition einer Methode durch Übergeben eines expliziten „definee“ und der Verwendung von class << self oder instance_eval . Los geht’s!

Klasseninstanzen und Instanzmethoden

Um zu verstehen, warum Metaklassen in Ruby verwendet werden, untersuchen wir zunächst die Unterschiede zwischen Instanz- und Klassenmethoden.

In Ruby ist eine Klasse ein Objekt, das einen Blueprint zum Erstellen anderer Objekte definiert. Klassen definieren, welche Methoden für eine Instanz dieser Klasse verfügbar sind.

Wenn Sie eine Methode in einer Klasse definieren, wird eine Instanzmethode für diese Klasse erstellt. Jede zukünftige Instanz dieser Klasse wird diese Methode zur Verfügung haben.

1 2 3 4 5 6 7 8 9 10 11 12
class User def initialize(name) @name = name end def name @name end end user = User.new('Thijs') user.name # => "Thijs" 

In diesem Beispiel erstellen wir eine Klasse mit dem Namen User mit einer Instanzmethode mit dem Namen #name, die den Namen des Benutzers zurückgibt. Mit der Klasse erstellen wir dann eine Klasseninstanz und speichern sie in einer Variablen mit dem Namen user . Da user eine Instanz der Klasse User ist, steht die Methode #name zur Verfügung.

Eine Klasse speichert ihre Instanzmethoden in ihrer Methodentabelle. Jede Instanz dieser Klasse verweist auf die Methodentabelle ihrer Klasse, um Zugriff auf ihre Instanzmethoden zu erhalten.

Klassenobjekte

Eine Klassenmethode ist eine Methode, die direkt in der Klasse aufgerufen werden kann, ohne zuerst eine Instanz erstellen zu müssen. Eine Klassenmethode wird erstellt, indem ihrem Namen bei der Definition self. vorangestellt wird.

Eine Klasse ist selbst ein Objekt. Eine Konstante bezieht sich auf das Klassenobjekt, sodass Klassenmethoden, die darauf definiert sind, von überall in der Anwendung aufgerufen werden können.

1 2 3 4 5 6 7 8 9
class User # ... def self.all end end User.all # => 

Methoden, die mit einem self. -Präfix definiert sind, werden nicht zur Methodentabelle der Klasse hinzugefügt. Sie werden stattdessen zur Metaklasse der Klasse hinzugefügt.

Metaklassen

Abgesehen von einer Klasse hat jedes Objekt in Ruby eine versteckte Metaklasse. Metaklassen sind Singletons, dh sie gehören zu einem einzelnen Objekt. Wenn Sie mehrere Instanzen einer Klasse erstellen, verwenden sie dieselbe Klasse, haben jedoch alle separate Metaklassen.

1 2 3 4 5 6 7 8 9
thijs, robert, tom = User.all thijs.class # => User robert.class # => User tom.class # => User thijs.singleton_class # => #<Class:#<User:0x00007fb71a9a2cb0>> robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>> tom.singleton_class # => #<Class:#<User:0x00007fb71a9a2c10>> 

In diesem Beispiel sehen wir, dass, obwohl jedes der Objekte die Klasse User hat, ihre Singleton-Klassen unterschiedliche Objekt-IDs haben, was bedeutet, dass sie separate Objekte sind.

Durch den Zugriff auf eine Metaklasse ermöglicht Ruby das Hinzufügen von Methoden direkt zu vorhandenen Objekten. Dadurch wird der Klasse des Objekts keine neue Methode hinzugefügt.

1 2 3 4 5 6 7 8
robert = User.new("Robert") def robert.last_name "Beekman" end robert.last_name # => "Beekman" User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>) 

In diesem Beispiel fügen wir dem in der Variablen robert gespeicherten Benutzer eine #last_name hinzu. Obwohl robert eine Instanz von User ist, haben alle neu erstellten Instanzen von User keinen Zugriff auf die Methode #last_name, da sie nur in der Metaklasse von robert vorhanden ist.

Was ist Selbst?

Wenn Sie eine Methode definieren und einen Empfänger übergeben, wird die neue Methode der Metaklasse des Empfängers hinzugefügt, anstatt sie der Methodentabelle der Klasse hinzuzufügen.

1 2 3 4 5
tom = User.new("Tom") def tom.last_name "de Bruijn" end 

Im obigen Beispiel haben wir #last_name direkt zum Objekt tom hinzugefügt, indem wir bei der Definition der Methode tom als Empfänger übergeben haben.

So funktioniert es auch für Klassenmethoden.

1 2 3 4 5 6 7
class User # ... def self.all end end 

Hier übergeben wir beim Erstellen der Methode .all explizit self als Empfänger. In einer Klassendefinition bezieht sich self auf die Klasse (in diesem FallUser ), sodass die .all -Methode zur Metaklasse von User hinzugefügt wird.

Da User ein Objekt ist, das in einer Konstanten gespeichert ist, greifen wir jedes Mal auf dasselbe Objekt — und dieselbe Metaklasse — zu, wenn wir darauf verweisen.

Öffnen der Metaklasse

Wir haben gelernt, dass Klassenmethoden Methoden in der Metaklasse des Klassenobjekts sind. Wenn wir das wissen, werden wir uns einige andere Techniken zum Erstellen von Klassenmethoden ansehen, die Sie vielleicht schon einmal gesehen haben.

class << self

Obwohl es etwas aus der Mode gekommen ist, verwenden einige Bibliotheken class << self , um Klassenmethoden zu definieren. Dieser Syntaxtrick öffnet die Metaklasse der aktuellen Klasse und interagiert direkt mit ihr.

1 2 3 4 5 6 7 8 9 10 11
class User class << self self # => #<Class:User> def all end end end User.all # => 

In diesem Beispiel wird eine Klassenmethode mit dem Namen User.all erstellt, indem der Metaklasse User eine Methode hinzugefügt wird. Anstatt explizit einen Empfänger für die Methode zu übergeben, wie wir zuvor gesehen haben, setzen wir self auf die Metaklasse von User anstelle von User selbst.

Wie wir bereits gelernt haben, wird jede Methodendefinition ohne expliziten Empfänger als Instanzmethode der aktuellen Klasse hinzugefügt. Innerhalb des Blocks ist die aktuelle Klasse die Metaklasse (#<Class:User>) von User .

instance_eval

Eine andere Option ist die Verwendung von instance_eval , was dasselbe mit einem großen Unterschied tut. Obwohl die Metaklasse der Klasse die im Block definierten Methoden empfängt, bleibt self ein Verweis auf die Hauptklasse.

1 2 3 4 5 6 7 8 9 10 11
class User instance_eval do self # => User def all end end end User.all # => 

In diesem Beispiel definieren wir eine Instanzmethode für die Metaklasse von User wie zuvor, aber self zeigt immer noch auf User . Obwohl es normalerweise auf dasselbe Objekt zeigt, können „default definee“ und self auf verschiedene Objekte zeigen.

Was wir gelernt haben

Wir haben gelernt, dass Klassen die einzigen Objekte sind, die Methoden haben können, und dass Instanzmethoden tatsächlich Methoden in der Metaklasse eines Objekts sind. Wir wissen, dass class << self einfach self vertauscht, damit Sie Methoden für die Metaklasse definieren können, und wir wissen, dass instance_eval größtenteils dasselbe tut (jedoch ohne self zu berühren).

Obwohl Sie nicht explizit mit Metaklassen arbeiten, verwendet Ruby sie ausgiebig unter der Haube. Zu wissen, was passiert, wenn Sie eine Methode definieren, kann Ihnen helfen zu verstehen, warum sich Ruby so verhält (und warum Sie Klassenmethoden mit self. voranstellen müssen).

Danke fürs Lesen. Wenn Ihnen das, was Sie gelesen haben, gefallen hat, können Sie Ruby Magic abonnieren, um eine E-Mail zu erhalten, wenn wir etwa einmal im Monat einen neuen Artikel veröffentlichen.



+