Unraveling Classes, Instances and Metaclasses in Ruby

Welcome to a new episode of Ruby Magic! Tämän kuun painos on kyse metaclasses, aihe herätti keskustelua kahden Kehittäjät (Hi Maud!).

metaklassikoita tutkimalla opimme, miten luokka-ja instanssimenetelmät toimivat Rubyssa. Matkan varrella selviää, mikä ero on menetelmän määrittelyllä suoralla ”defineellä”ja käyttämällä class << self tai instance_eval. Mennään!

Luokka-instanssit ja Instanssimenetelmät

ymmärtääksemme, miksi metaklassikoita käytetään Rubyssa, aloitamme tutkimalla, mitkä erot ovat instanssi-ja luokkamenetelmien välillä.

Rubyssa luokka on objekti, joka määrittelee piirustuksen muiden objektien luomiseksi. Luokat määrittelevät, mitkä menetelmät ovat käytettävissä missä tahansa kyseisen luokan esiintymässä.

menetelmän määrittely luokan sisällä luo instanssimenetelmän kyseiselle luokalle. Kaikilla tämän luokan tulevilla esiintymillä on käytössään kyseinen menetelmä.

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" 

tässä esimerkissä luodaan luokka nimeltä User, jonka instanssimenetelmä on nimeltään #name, joka palauttaa käyttäjän nimen. Luokan avulla luomme sitten luokka-instanssin ja tallennamme sen muuttujaan, jonka nimi on user. Koska user on User – luokan instanssi, sillä on käytössä #name – menetelmä.

A-luokka tallentaa instanssimenetelmänsä menetelmätaulukkoonsa. Mikä tahansa kyseisen luokan esiintymä viittaa sen luokan menetelmätaulukkoon päästäkseen käsiksi sen instanssimenetelmiin.

Luokkaobjektit

luokkamenetelmä on menetelmä, jota voidaan kutsua suoraan luokkaan ilman, että tarvitsee ensin luoda esiintymä. Luokkamenetelmä syntyy, kun sen nimi merkitään prefiksillä self. sitä määriteltäessä.

luokka on itsessään objekti. Vakiolla tarkoitetaan luokkaobjektia, joten sille määriteltyjä luokkamenetelmiä voidaan kutsua mistä tahansa sovelluksesta.

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

self. – etuliitteellä määriteltyjä menetelmiä ei lisätä luokan menetelmätaulukkoon. Ne sen sijaan lisätään luokan’ metaclass.

Metaklassikot

luokkaa lukuun ottamatta jokaisella rubiinin esineellä on piilotettu metaklassikko. Metaklassikot ovat singletoneja, eli ne kuuluvat yhteen kappaleeseen. Jos luot useita esimerkkejä luokasta, he jakavat saman luokan, mutta heillä kaikilla on erilliset metaklassot.

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

tässä esimerkissä näemme, että vaikka jokaisella oliolla on luokka User, niiden Singletonin luokilla on eri oliotunnukset, eli ne ovat erillisiä objekteja.

käyttämällä metaklassia Ruby mahdollistaa menetelmien lisäämisen suoraan olemassa oleviin olioihin. Näin ei lisätä uutta menetelmää olion luokkaan.

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

tässä esimerkissä lisätään #last_name käyttäjälle, joka on tallennettu muuttujaan robert. Vaikka robert on User: n instanssi, eivät uudet User: n instanssit pääse #last_name – metodiin, koska se on olemassa vain robert: n metaklassikossa.

mikä on itse?

menetelmää määriteltäessä ja vastaanotinta läpäistäessä uusi menetelmä lisätään vastaanottajan metaklassiin sen sijaan, että se lisättäisiin luokan ” menetelmätaulukkoon.

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

yllä olevassa esimerkissä olemme lisänneet #last_name suoraan tom objektille, ohittamalla tom vastaanottajaksi menetelmää määriteltäessä.

näin se toimii myös luokkamenetelmissä.

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

tässä mennään nimenomaan self vastaanottimena .all – menetelmää luotaessa. Luokkamääritelmässä self viittaa luokkaan (tässä tapauksessaUser), joten .all – menetelmä lisätään User: n metaklassiin.

koska User on objekti, joka on tallennettu vakioon, pääsemme samaan objektiin – ja samaan metaklassiin-aina kun viittaamme siihen.

Metaklassikon avaaminen

olemme oppineet, että luokkamenetelmät ovat luokkaobjektin metaklassikon menetelmiä. Tietäen tämän, me tarkastelemme joitakin muita tekniikoita luoda luokan menetelmiä, että olet ehkä nähnyt ennen.

Luokka << self

vaikka se on mennyt hieman muodista, jotkut kirjastot käyttävät class << self luokkamenetelmien määrittelyyn. Tämä syntaksitemppu avaa nykyisen luokan metaklassikon ja vuorovaikuttaa sen kanssa suoraan.

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

tässä esimerkissä luodaan User.all – niminen luokkamenetelmä lisäämällä menetelmä User: n metaklassiin. Sen sijaan, että olisimme eksplisiittisesti siirtäneet menetelmän vastaanottimen, kuten aiemmin näimme, asetimme self User: n metaklassiin User: n sijaan itse.

kuten aiemmin opimme, mikä tahansa metodimääritys ilman eksplisiittistä vastaanotinta lisätään nykyisen luokan instanssimenetelmäksi. Lohkon sisällä nykyinen luokka on User ’ s metaclass (#<Class:User>).

instance_eval

toinen vaihtoehto on käyttää instance_eval, joka tekee saman asian yhdellä merkittävällä erolla. Vaikka luokan metaklassikko saa lohkossa määritellyt menetelmät, self on edelleen viittaus pääluokkaan.

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

tässä esimerkissä määritellään instanssimenetelmä User: n metaklassikolle aivan kuten ennenkin, mutta self viittaa silti User: ään. Vaikka se yleensä viittaa samaan kohteeseen, ”definee” ja self voivat viitata eri olioihin.

mitä olemme oppineet

olemme oppineet, että luokat ovat ainoita olioita, joilla voi olla metodeja, ja että instanssimenetelmät ovat itse asiassa metodeja olion metaklassikossa. Tiedämme, että class << self yksinkertaisesti vaihtuu self ympäri, jotta metaklassilla voi määritellä menetelmiä, ja tiedämme, että instance_eval tekee enimmäkseen saman asian (mutta koskematta self).

vaikka et nimenomaisesti työskentele metaklassikoiden kanssa, Ruby käyttää niitä laajasti konepellin alla. Tietäen, mitä tapahtuu, kun määrittelet menetelmän, voit ymmärtää, miksi Ruby käyttäytyy kuten se käyttäytyy (ja miksi sinun täytyy etuliite luokan menetelmiä self.).

Kiitos lukemisesta. Jos pidit lukemastasi, saatat haluta tilata Ruby Magic-sivuston saadaksesi sähköpostia, kun julkaisemme uuden artikkelin noin kerran kuukaudessa.



+