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_eval
te 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 self
aan 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.