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 self
till 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å User
metaklass 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_eval
gö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.