Unraveling Classes, Instances and Metaclasses in Ruby

Welkom bij een nieuwe aflevering van Ruby Magic! De editie van deze maand gaat over metaclasses, een onderwerp aangewakkerd door een discussie tussen twee ontwikkelaars (Hi Maud!).

door metaclasses te onderzoeken, leren we hoe class en instance methoden werken in Ruby. Ontdek onderweg het verschil tussen het definiëren van een methode door een expliciete “definee” aan te geven en class << self of instance_evalte gebruiken. Laten we gaan!

Class Instances en Instance Methods

om te begrijpen waarom metaclasses worden gebruikt in Ruby, zullen we beginnen met het onderzoeken van de verschillen tussen instance – en class methoden.

in Ruby is een klasse Een object dat een blauwdruk definieert om andere objecten te maken. Klassen bepalen welke methoden beschikbaar zijn op een instantie van die klasse.

het definiëren van een methode binnen een klasse maakt een instantie methode op die klasse. Elke toekomstige instantie van die klasse zal die methode beschikbaar hebben.

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 dit voorbeeld maken we een klasse met de naam User, met een instantiemethode met de naam #name die de naam van de gebruiker retourneert. Met behulp van de class maken we vervolgens een class-instantie en slaan deze op in een variabele met de naam user. Aangezien user een instantie van de klasse User is, heeft het de methode #name beschikbaar.

een klasse slaat zijn instantiemethoden op in zijn method table. Elk exemplaar van die klasse verwijst naar de method table van zijn klasse om toegang te krijgen tot zijn instance methoden.

Class Objects

een class method is een methode die direct op de class aangeroepen kan worden zonder eerst een instantie aan te maken. Een class methode wordt gemaakt door zijn naam voor te schrijven met self. bij het definiëren ervan.

een klasse is zelf een object. Een constante verwijst naar het klasse-object, dus klassenmethoden die erop zijn gedefinieerd, kunnen overal in de toepassing worden aangeroepen.

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

methoden gedefinieerd met een self. – prefix worden niet toegevoegd aan de method table van de klasse. Ze worden toegevoegd aan de metaclass van de klas.

Metaclasses

afgezien van een klasse heeft elk object in Ruby een verborgen metaclass. Metaclasses zijn singletons, wat betekent dat ze tot een enkel object behoren. Als je meerdere instanties van een klasse maakt, zullen ze dezelfde klasse delen, maar ze zullen allemaal aparte metaclasses hebben.

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 dit voorbeeld zien we dat, hoewel elk van de objecten de klasse User heeft, hun singleton-klassen verschillende object-ID ‘ s hebben, wat betekent dat ze afzonderlijke objecten zijn.

door toegang te hebben tot een metaclass, maakt Ruby het mogelijk om methoden rechtstreeks toe te voegen aan bestaande objecten. Dit zal geen nieuwe methode toevoegen aan de klasse van het object.

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 dit voorbeeld voegen we een #last_name toe aan de gebruiker die is opgeslagen in de robert variabele. Hoewel robert een instantie van User is, hebben nieuw aangemaakte instanties van User geen toegang tot de #last_name methode, omdat deze alleen bestaat op metaclass van robert.

Wat is self?

bij het definiëren van een methode en het passeren van een ontvanger, wordt de nieuwe methode toegevoegd aan de metaclass van de ontvanger, in plaats van deze toe te voegen aan de tabel van de klasse.

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

in het voorbeeld hierboven hebben we #last_name direct toegevoegd aan het tom object, door tom als ontvanger door te geven bij het definiëren van de methode.

Dit is ook hoe het werkt voor klassemethoden.

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

hier geven we expliciet self door als een ontvanger bij het maken van de .all methode. In een klasse definitie, self verwijst naar de klasse (User in dit geval), dus de .all methode wordt toegevoegd aan User’s metaclass.

omdat User een object is dat is opgeslagen in een constante, krijgen we toegang tot hetzelfde object—en dezelfde metaclass—wanneer we ernaar verwijzen.

Opening van de Metaclass

we hebben geleerd dat klassenmethoden methoden zijn in de metaclass van het class-object. Dit wetende, zullen we kijken naar een aantal andere technieken van het creëren van klasse methoden die je eerder zou kunnen hebben gezien.

klasse << zelf

hoewel het een beetje uit de mode is geraakt, gebruiken sommige bibliotheken class << self om klassenmethoden te definiëren. Deze syntaxis Truc opent de metaclass van de huidige klasse en interageert er direct mee.

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 # => 

dit voorbeeld maakt een klasse methode genaamd User.all door een methode toe te voegen aan User’s metaclass. In plaats van expliciet Een ontvanger voor de methode door te geven zoals we eerder zagen, zetten we self op User’s metaclass in plaats van User zelf.

zoals we eerder hebben geleerd, wordt elke methode definitie zonder een expliciete ontvanger toegevoegd als een instantie methode van de huidige klasse. Binnen het blok is de huidige klasse User’s metaclass (#<Class:User>).

instance_eval

een andere optie is door instance_eval te gebruiken, wat hetzelfde doet met één groot verschil. Hoewel de metaclass van de klasse de in het blok gedefinieerde methoden ontvangt, blijft self een verwijzing naar de Hoofdklasse.

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 dit voorbeeld definiëren we een instantie methode op User’s metaclass net als voorheen, maar self wijst nog steeds naar User. Hoewel het meestal naar hetzelfde object wijst, kunnen de “default definee” en self naar verschillende objecten wijzen.

wat we geleerd hebben

we hebben geleerd dat klassen de enige objecten zijn die methoden kunnen hebben, en dat instantiemethoden eigenlijk methoden zijn op de metaclass van een object. We weten dat class << self gewoon self omwisselt om methoden op de metaclass te definiëren, en we weten dat instance_eval meestal hetzelfde doet (maar zonder selfaan te raken).

hoewel u niet expliciet met metaclasses wilt werken, gebruikt Ruby ze uitgebreid onder de motorkap. Weten wat er gebeurt als je een methode definieert kan je helpen begrijpen waarom Ruby zich gedraagt zoals het doet (en waarom je klassenmethoden moet prefixen met self.).

Bedankt voor het lezen. Als je het leuk vond wat je leest, kun je je abonneren op Ruby Magic om een e-mail te ontvangen wanneer we ongeveer een keer per maand een nieuw artikel publiceren.



+