Unraveling klasser, instanser och Metaclasses i Ruby

Välkommen till en ny episod av Ruby Magic! Den här månadens utgåva handlar om metaclasses, ett ämne som utlöstes av en diskussion mellan två utvecklare (Hej Maud!).

genom att undersöka metaclasses lär vi oss hur klass-och instansmetoder fungerar i Ruby. Längs vägen, upptäck skillnaden mellan att definiera en metod genom att skicka en explicit ”definee” och använda class << self eller instance_eval. Kom igen!

Klassinstanser och Instansmetoder

för att förstå varför metaclasses används i Ruby, börjar vi med att undersöka vad skillnaderna är mellan instans – och klassmetoder.

i Ruby är en klass ett objekt som definierar en ritning för att skapa andra objekt. Klasser definierar vilka metoder som är tillgängliga på någon instans av den klassen.

definiera en metod i en klass skapar en instansmetod på den klassen. Varje framtida instans av den klassen kommer att ha den metoden tillgänglig.

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 det här exemplet skapar vi en klass med namnet User, med en instansmetod med namnet #name som returnerar användarens namn. Med klassen skapar vi sedan en klassinstans och lagrar den i en variabel med namnet user. Eftersom user är en instans av klassen User har den #name – metoden tillgänglig.

en klass lagrar sina instansmetoder i sin metodtabell. Varje instans av den klassen hänvisar till klassens metodtabell för att få tillgång till dess instansmetoder.

klassobjekt

en klassmetod är en metod som kan anropas direkt på klassen utan att behöva skapa en instans först. En klassmetod skapas genom att prefixa sitt namn med self. när man definierar det.

en klass är i sig ett objekt. En konstant hänvisar till klassobjektet, så klassmetoder som definieras på det kan anropas var som helst i applikationen.

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

metoder definierade med ett self. – prefix läggs inte till i klassens metodtabell. De läggs istället till klassens metaklass.

Metaclasses

bortsett från en klass har varje objekt i Ruby en dold metaklass. Metaclasses är singletons, vilket betyder att de tillhör ett enda objekt. Om du skapar flera instanser av en klass delar de samma klass, men de har alla separata 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 det här exemplet ser vi att även om varje objekt har klassen User, har deras singleton-klasser olika objekt-ID, vilket betyder att de är separata objekt.

genom att ha tillgång till en metaklass tillåter Ruby att lägga till metoder direkt till befintliga objekt. Om du gör det kommer inte att lägga till en ny metod till objektets klass.

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 det här exemplet lägger vi till en #last_name till användaren som är lagrad i variabeln robert. Även om robert är en instans av User, kommer alla nyskapade instanser av User inte att ha tillgång till #last_name – metoden, eftersom den bara finns på robert metaclass.

vad är själv?

när du definierar en metod och skickar en mottagare läggs den nya metoden till mottagarens metaklass istället för att lägga till den i klassens metodtabell.

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

i exemplet ovan har vi lagt till #last_name direkt på objektet tom genom att skicka tom som mottagare när metoden definieras.

så här fungerar det också för klassmetoder.

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

här passerar vi uttryckligen self som mottagare när du skapar .all – metoden. I en klassdefinition hänvisar self till klassen (User i det här fallet), så .all – metoden läggs till i User metaklass.

eftersom User är ett objekt som lagras i en konstant, kommer vi åt samma objekt—och samma metaklass-när vi refererar till det.

öppna Metaklassen

vi har lärt oss att klassmetoder är metoder i klassobjektets metaklass. Att veta detta kommer vi att titta på några andra tekniker för att skapa klassmetoder som du kanske har sett tidigare.

klass << själv

även om det har gått ur stil lite, använder vissa bibliotek class << self för att definiera klassmetoder. Detta syntaxtrick öppnar den aktuella klassens metaklass och interagerar direkt 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 # => 

i det här exemplet skapas en klassmetod med namnet User.all genom att lägga till en metod i User’s metaclass. Istället för att uttryckligen skicka en mottagare för metoden som vi såg tidigare, satte vi selftill User s metaclass istället för User själv.

som vi lärde oss tidigare, läggs någon metoddefinition utan en explicit mottagare till som en instansmetod för den aktuella klassen. Inne i blocket är den aktuella klassen User ’ s metaclass (#<Class:User>).

instance_eval

ett annat alternativ är att använda instance_eval, vilket gör samma sak med en stor skillnad. Även om klassens metaklass får de metoder som definieras i blocket, self förblir en referens till huvudklassen.

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 det här exemplet definierar vi en instansmetod på Usermetaklass precis som tidigare, men self pekar fortfarande på User. Även om det vanligtvis pekar på samma objekt kan” default definee ” och self peka på olika objekt.

vad vi har lärt oss

vi har lärt oss att klasser är de enda objekt som kan ha metoder, och att instansmetoder faktiskt är metoder på ett objekts metaklass. Vi vet att class << self helt enkelt byter self runt så att du kan definiera metoder på metaklassen, och vi vet att instance_evalgör mestadels samma sak (men utan att röra self).

även om du inte uttryckligen arbetar med metaclasses, använder Ruby dem i stor utsträckning under huven. Att veta vad som händer när du definierar en metod kan hjälpa dig att förstå varför Ruby beter sig som det gör (och varför du måste prefixa klassmetoder med self.).

Tack för att du läste. Om du gillade det du läste kanske du vill prenumerera på Ruby Magic för att få ett e-postmeddelande när vi publicerar en ny artikel ungefär en gång i månaden.



+