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.