Unraveling klasser, forekomster og Metaclasses i Ruby

velkommen til en ny episode af Ruby Magic! Denne måneds udgave handler om metaclasses, et emne udløst af en diskussion mellem to udviklere (Hej Maud!).

ved at undersøge metaclasses lærer vi, hvordan klasse-og instansmetoder fungerer i Ruby. Undervejs opdager du forskellen mellem at definere en metode ved at sende en eksplicit “definee” og bruge class << self eller instance_eval. Kom så!

klasse instanser og Instansmetoder

for at forstå, hvorfor metaclasses bruges i Ruby, starter vi med at undersøge, hvad forskellene er mellem instans – og klassemetoder.

i Ruby er en klasse et objekt, der definerer en plan for at oprette andre objekter. Klasser definerer, hvilke metoder der er tilgængelige på enhver forekomst af denne klasse.

definition af en metode i en klasse opretter en forekomstmetode på den klasse. Enhver fremtidig forekomst af denne klasse vil have denne metode tilgængelig.

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" 

i dette eksempel opretter vi en klasse med navnet User med en forekomstmetode med navnet #name, der returnerer brugerens navn. Ved hjælp af klassen opretter vi derefter en klasseforekomst og gemmer den i en variabel med navnet user. Da user er en forekomst af User – klassen, har den #name – metoden tilgængelig.

en klasse gemmer sine instansmetoder i sin metodetabel. Enhver forekomst af denne klasse henviser til dens klasses metodetabel for at få adgang til dens instansmetoder.

Klasseobjekter

en klassemetode er en metode, der kan kaldes direkte på klassen uden først at skulle oprette en forekomst. En klassemetode oprettes ved at præfiksere sit navn med self., når det defineres.

en klasse er i sig selv et objekt. En konstant refererer til klasseobjektet, så klassemetoder defineret på det kan kaldes hvor som helst i applikationen.

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

metoder defineret med et self. – præfiks føjes ikke til klassens metodetabel. De er i stedet tilføjet til klassens metaklasse.

Metaclasses

bortset fra en klasse har hvert objekt i Ruby en skjult metaklasse. Metaklasser er singletoner, hvilket betyder, at de tilhører et enkelt objekt. Hvis du opretter flere forekomster af en klasse, deler de den samme klasse, men de har alle separate metaklasser.

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

i dette eksempel ser vi, at selvom hvert af objekterne har klassen User, har deres singleton-klasser forskellige objekt-id ‘ er, hvilket betyder, at de er separate objekter.

ved at have adgang til en metaklasse tillader Ruby at tilføje metoder direkte til eksisterende objekter. Hvis du gør det, tilføjes ikke en ny metode til objektets klasse.

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>) 

i dette eksempel tilføjer vi en #last_name til brugeren, der er gemt i variablen robert. Selvom robert er en forekomst af User, vil eventuelle nyoprettede forekomster af User ikke have adgang til #last_name – metoden, da den kun findes på robert ‘s metaklasse.

Hvad er selv?

når man definerer en metode og sender en modtager, tilføjes den nye metode til modtagerens metaklasse i stedet for at tilføje den til klassens metodetabel.

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

i eksemplet ovenfor har vi tilføjet #last_name direkte på tom objektet ved at passere tom som modtageren, når metoden defineres.

Sådan fungerer det også for klassemetoder.

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

her passerer vi eksplicit self som modtager, når vi opretter .all – metoden. I en klassedefinition henviser self til klassen (User i dette tilfælde), så .all – metoden føjes til User‘s metaklasse.

fordi User er et objekt gemt i en konstant, får vi adgang til det samme objekt—og den samme metaklasse—når vi refererer til det.

åbning af Metaklassen

vi har lært, at klassemetoder er metoder i klasseobjektets metaklasse. Når vi kender dette, vil vi se på nogle andre teknikker til at skabe klassemetoder, som du måske har set før.

klasse << selv

selvom det er gået lidt ud af stil, bruger nogle biblioteker class << self til at definere klassemetoder. Dette syntakstrick åbner den nuværende klasses metaklasse og interagerer direkte med den.

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

dette eksempel opretter en klassemetode med navnet User.allved at tilføje en metode til User ‘s metaklasse. I stedet for eksplicit at sende en modtager til metoden, som vi så tidligere, satte vi self til User‘s metaklasse i stedet for User selv.

som vi lærte før, tilføjes enhver metodedefinition uden en eksplicit modtager som en forekomstmetode for den aktuelle klasse. Inde i blokken er den nuværende klasse User ‘ s metaklasse (#<Class:User>).

instance_eval

en anden mulighed er ved at bruge instance_eval, som gør det samme med en stor forskel. Selvom klassens metaklasse modtager de metoder, der er defineret i blokken, forbliver self en henvisning til hovedklassen.

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

i dette eksempel definerer vi en forekomstmetode på User‘s metaklasse ligesom før, men self peger stadig på User. Selvom det normalt peger på det samme objekt, kan “default definee” og self pege på forskellige objekter.

hvad vi har lært

vi har lært, at klasser er de eneste objekter, der kan have metoder, og at instansmetoder faktisk er metoder på et objekts metaklasse. Vi ved, at class << self simpelthen bytter self rundt for at give dig mulighed for at definere metoder på metaklassen, og vi ved, at instance_evalgør det meste det samme (men uden at røre self).

selvom du ikke eksplicit arbejder med metaclasses, bruger Ruby dem i vid udstrækning under hætten. At vide, hvad der sker, når du definerer en metode, kan hjælpe dig med at forstå, hvorfor Ruby opfører sig som den gør (og hvorfor du skal præfiks klassemetoder med self.).

tak for læsning. Hvis du kunne lide det, du læste, kan du abonnere på Ruby Magic for at modtage en e-mail, når vi udgiver en ny artikel om en gang om måneden.



+