Unraveling clase, instanțe și Metaclasses în Ruby

Bine ati venit la un nou episod de Ruby Magic! Ediția din această lună este despre metaclasses, un subiect declanșat de o discuție între doi dezvoltatori (Hi Maud!).

prin examinarea metaclaselor, vom învăța cum funcționează metodele de clasă și instanță în Ruby. Pe parcurs, descoperiți diferența dintre definirea unei metode prin trecerea unui „definee” explicit și utilizarea class << self sau instance_eval. Să mergem!

instanțe de clasă și metode de instanță

pentru a înțelege de ce metaclasses sunt utilizate în Ruby, vom începe prin a examina care sunt diferențele dintre metodele de instanță și clasă.

în Ruby, o clasă este un obiect care definește un plan pentru a crea alte obiecte. Clasele definesc ce metode sunt disponibile pe orice instanță a clasei respective.

definirea unei metode în interiorul unei clase creează o metodă instanță pe acea clasă. Orice instanță viitoare a acestei clase va avea această metodă disponibilă.

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" 

în acest exemplu, creăm o clasă numită User, cu o metodă de instanță numită #name care returnează numele utilizatorului. Folosind clasa, creăm o instanță de clasă și o stocăm într-o variabilă numită user. Deoarece user este o instanță a clasei User, are disponibilă metoda #name.

o clasă stochează metodele sale de instanță în tabelul său de metode. Orice instanță a acelei clase se referă la tabelul metodei clasei sale pentru a avea acces la metodele sale de instanță.

obiecte de clasă

o metodă de clasă este o metodă care poate fi apelată direct pe clasă fără a fi nevoie să creați mai întâi o instanță. O metodă de clasă este creată prin prefixarea numelui său cu self. la definirea acesteia.

o clasă este ea însăși un obiect. O constantă se referă la obiectul de clasă, astfel încât metodele de clasă definite pe acesta pot fi apelate de oriunde din aplicație.

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

metodele definite cu un self. – prefix nu sunt adăugate la tabelul metodei clasei. În schimb, sunt adăugate la metaclasa clasei.

Metaclasses

în afară de o clasă, fiecare obiect din Ruby are o metaclasă ascunsă. Metaclasses sunt singletons, ceea ce înseamnă că aparțin unui singur obiect. Dacă creați mai multe instanțe ale unei clase, acestea vor partaja aceeași clasă, dar toate vor avea metaclase separate.

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

în acest exemplu, vedem că, deși fiecare dintre obiecte are clasa User, clasele lor singleton au ID-uri de obiecte diferite, ceea ce înseamnă că sunt obiecte separate.

având acces la o metaclasă, Ruby permite adăugarea de metode Direct la obiectele existente. Acest lucru nu va adăuga o nouă metodă la clasa obiectului.

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

în acest exemplu, adăugăm un #last_name utilizatorului stocat în variabila robert. Deși robert este o instanță de User, orice instanțe nou create de User nu vor avea acces la metoda #last_name, deoarece există doar pe metaclasa robert.

ce este sinele?

la definirea unei metode și trecerea unui receptor, noua metodă este adăugată la metaclasa receptorului, în loc să o adauge la tabelul metodei clasei.

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

în exemplul de mai sus, am adăugat #last_name direct pe obiectul tom, trecând tom ca receptor la definirea metodei.

acesta este și modul în care funcționează pentru metodele de clasă.

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

aici, trecem în mod explicit self ca receptor atunci când creăm metoda .all. Într-o definiție de clasă, self se referă la clasa (User în acest caz), astfel încât metoda .all se adaugă la metaclasa User.

deoarece User este un obiect stocat într—o constantă, vom accesa același obiect—și aceeași metaclasă-ori de câte ori îl vom face referire.

deschiderea Metaclass

am învățat că metodele de clasă sunt metode în metaclass obiectului clasei. Știind acest lucru, ne vom uita la alte tehnici de creare a metodelor de clasă pe care le-ați văzut înainte.

class<< self

deși a ieșit din Stil un pic, unele biblioteci folosesc class << self pentru a defini metodele de clasă. Acest truc de sintaxă deschide metaclasa clasei curente și interacționează direct cu ea.

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

acest exemplu creează o metodă de clasă numită User.allprin adăugarea unei metode la metaclasa User. În loc de a trece în mod explicit un receptor pentru metoda așa cum am văzut anterior, am stabilit self la User‘s metaclass în loc de User în sine.

după cum am învățat mai devreme, orice definiție a metodei fără un receptor explicit este adăugată ca metodă de instanță a clasei curente. În interiorul blocului, clasa curentă este User ‘ s metaclass (#<Class:User>).

instance_eval

o altă opțiune este prin utilizarea instance_eval, care face același lucru cu o diferență majoră. Deși metaclasa clasei primește metodele definite în bloc, self rămâne o referință la clasa principală.

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

în acest exemplu, definim o metodă de instanță pe metaclasa User la fel ca înainte, dar selfindică încă User. Deși indică de obicei același obiect, „definee implicit” și self pot indica obiecte diferite.

ce am învățat

am învățat că clasele sunt singurele obiecte care pot avea metode și că metodele de instanță sunt de fapt metode pe metaclasa unui obiect. Știm că class << self schimbă pur și simplu self pentru a vă permite să definiți metode pe metaclasă și știm că instance_evalface în mare parte același lucru (dar fără a atinge self).

deși nu veți lucra în mod explicit cu metaclasses, Ruby le folosește pe scară largă sub capotă. Știind ce se întâmplă atunci când definiți o metodă vă poate ajuta să înțelegeți de ce Ruby se comportă așa cum o face (și de ce trebuie să prefixați metodele de clasă cu self.).

Vă mulțumim pentru lectură. Dacă ți-a plăcut ceea ce ai citit, s-ar putea să te abonezi la Ruby Magic pentru a primi un e-mail atunci când publicăm un articol nou aproximativ o dată pe lună.



+