Pogosto slišite, da je metaprogramiranje nekaj, kar uporabljajo le Ruby nindže in da preprosto ni za navadne smrtnike. Toda resnica je, da metaprogramiranje sploh ni nekaj strašljivega. Ta objava v spletnem dnevniku bo služila izzivu tovrstnega razmišljanja in približanju metaprogramiranja povprečnemu razvijalcu Rubyja, da bodo lahko tudi izkoristili njegove prednosti.
Ruby metaprogramiranje: koda za pisanje kode TweetTreba je opozoriti, da bi lahko metaprogramiranje veliko pomenilo in ga je pogosto mogoče zelo zlorabiti in iti do skrajnosti, ko gre za uporabo, zato bom poskusil navesti nekaj primerov iz resničnega sveta, ki bi jih lahko vsak uporabil pri vsakodnevnem programiranju.
Metaprogramiranje je tehnika, s katero lahko napišete kodo, ki piše koda sama dinamično med izvajanjem. To pomeni, da lahko med izvajanjem definirate metode in razrede. Noro, kajne? Na kratko, z metaprogramiranjem lahko znova odprete in spremenite razrede, ujamete metode, ki ne obstajajo, in jih sproti ustvarite, ustvarite kodo, ki je SUHO z izogibanjem ponovitvam in še več.
Preden se poglobimo v resno metaprogramiranje, moramo raziskati osnove. In najboljši način za to je z zgledom. Začnimo z enim in razumemo Rubyjevo metaprogramiranje po korakih. Verjetno lahko ugibate, kaj počne ta koda:
class Developer def self.backend 'I am backend developer' end def frontend 'I am frontend developer' end end
Opredelili smo razred z dvema metodama. Prva metoda v tem razredu je metoda razreda, druga pa metoda primerka. To je osnovna stvar v Rubyju, vendar se za to kodo dogaja veliko več, kar moramo razumeti, preden nadaljujemo. Omeniti velja, da je razred Developer
sam je dejansko objekt. V Rubyju je vse predmet, vključno z razredi. Ker Developer
je primerek, je primerek razreda Class
. Evo, kako izgleda objektni model Ruby:
p Developer.class # Class p Class.superclass # Module p Module.superclass # Object p Object.superclass # BasicObject
Pomembno je, da tukaj razumemo pomen self
. The frontend
metoda je običajna metoda, ki je na voljo v primerkih razreda Developer
, zakaj pa je backend
metoda razredna metoda? Vsak del kode, izveden v Rubyju, se izvrši proti določenemu sebe . Ko interpreter Ruby izvede katero koli kodo, vedno zabeleži vrednost self
za katero koli določeno vrstico. self
se vedno nanaša na nek objekt, vendar se lahko ta objekt spremeni na podlagi izvedene kode. Na primer, znotraj definicije razreda je self
se nanaša na sam razred, ki je primerek razreda Class
.
class Developer p self end # Developer
Metode znotraj primerka, self
se nanaša na primerek razreda.
class Developer def frontend self end end p Developer.new.frontend # #
Znotraj metod razreda, self
se na nek način nanaša na sam razred (o katerem bomo podrobneje govorili v nadaljevanju tega članka):
class Developer def self.backend self end end p Developer.backend # Developer
To je v redu, kaj pa je razredna metoda? Preden odgovorimo na to vprašanje, moramo omeniti obstoj nečesa, čemur pravimo metaklasa, znana tudi kot singleton razred in lastni razred. Razredna metoda frontend
kar smo prej definirali, ni nič drugega kot metoda primerka, definirana v metaklasi za objekt Developer
! Metaklas je v bistvu razred, ki ga Ruby ustvari in vstavi v hierarhijo dedovanja, da zadrži metode razreda, s čimer ne posega v primerke, ustvarjene iz razreda.
Vsak predmet v Rubyju ima svojega metaklasa . Razvijalcu je nekako neviden, vendar je tam in ga lahko zelo enostavno uporabite. Od našega razreda Developer
je v bistvu objekt, ima svoj metaklas. Kot primer ustvarimo objekt razreda String
in manipulira z njegovim metaklasom:
example = 'I'm a string object' def example.something self.upcase end p example.something # I'M A STRING OBJECT
Kar smo storili tukaj, smo dodali singleton metodo something
na predmet. Razlika med metodami razreda in metodami singleton je v tem, da so metode razreda na voljo vsem primerkom predmeta razreda, medtem ko so metode singletona na voljo samo temu posameznemu primerku. Metode razredov se pogosto uporabljajo, medtem ko se enojne metode ne uporabljajo toliko, vendar sta obe vrsti metod dodani v metarazred tega predmeta.
Prejšnji primer bi lahko na novo napisali tako:
example = 'I'm a string object' class << example def something self.upcase end end
Sintaksa je drugačna, vendar dejansko naredi isto. Zdaj se vrnimo na prejšnji primer, kjer smo ustvarili Developer
razreda in raziščite nekatere druge sintakse, da definirate metodo razreda:
class Developer def self.backend 'I am backend developer' end end
To je osnovna definicija, ki jo uporabljajo skoraj vsi.
def Developer.backend 'I am backend developer' end
To je ista stvar, mi definiramo backend
metoda razreda za Developer
. Nismo uporabili self
toda z definiranjem takšne metode je to dejansko metoda razreda.
class Developer class << self def backend 'I am backend developer' end end end
Spet definiramo metodo razreda, vendar uporabljamo sintakso, podobno tisti, ki smo jo uporabili za določitev enojne metode za String
predmet. Morda boste opazili, da smo uporabili self
tukaj se nanaša na Developer
predmet sam. Najprej smo odprli Developer
razreda, tako da je samo enak Developer
razred. Nato naredimo class << self
in postanemo enaki metaklasu Developer
Nato določimo metodo backend
na Developer
metaklasi.
class << Developer def backend 'I am backend developer' end end
Z definiranjem takega bloka nastavimo self
do metaklase Developer
za čas trajanja bloka. Kot rezultat se backend
metoda je dodana v metarazred Developer
in ne v sam razred.
Poglejmo, kako se ta metaklas obnaša v drevesu dedovanja:
Kot ste videli v prejšnjih primerih, ni pravega dokaza, da metaklasa sploh obstaja. Lahko pa uporabimo majhen kramp, ki nam lahko pokaže obstoj tega nevidnega razreda:
class Object def metaclass_example class << self self end end end
Če določimo metodo primerka v Object
razred (da, kateri koli razred lahko kadar koli znova odpremo, to je še ena lepota metaprogramiranja), imeli bomo self
ki se nanaša na Object
predmet v njem. Nato lahko uporabimo class << self
sintaksa za spremembo trenutne sebe da kaže na metaklaso trenutnega predmeta. Ker je trenutni objekt Object
samega razreda, bi bil to metaklasa primerka. Metoda vrne self
ki je v tem trenutku sam metaklas. S klicem te metode primerka na katerem koli objektu lahko dobimo metaklas tega predmeta. Določimo naše Developer
ponovno poučite in začnite malo raziskovati:
class Developer def frontend p 'inside instance method, self is: ' + self.to_s end class << self def backend p 'inside class method, self is: ' + self.to_s end end end developer = Developer.new developer.frontend # 'inside instance method, self is: #' Developer.backend # 'inside class method, self is: Developer' p 'inside metaclass, self is: ' + developer.metaclass_example.to_s # 'inside metaclass, self is: #'
In za krešendo poglejmo dokaz, da frontend
je metoda primerka razreda in backend
je metoda primerka metaklase:
p developer.class.instance_methods false # [:frontend] p developer.class.metaclass_example.instance_methods false # [:backend]
Čeprav za pridobitev metaklasa ni treba dejansko znova odpreti Object
in dodajte ta kramp. Uporabite lahko singleton_class
ki jih Ruby zagotavlja. Je enako kot metaclass_example
dodali smo, toda s tem krampom lahko dejansko vidite, kako Ruby deluje pod pokrovom:
p developer.class.singleton_class.instance_methods false # [:backend]
Obstaja še en način za ustvarjanje metode predavanja, in sicer z uporabo instance_eval
:
class Developer end Developer.instance_eval do p 'instance_eval - self is: ' + self.to_s def backend p 'inside a method self is: ' + self.to_s end end # 'instance_eval - self is: Developer' Developer.backend # 'inside a method self is: Developer'
Ta del kode, ki ga interpreter Ruby oceni v kontekstu primerka, ki je v tem primeru Developer
predmet. In ko definirate metodo za objekt, ustvarite bodisi metodo razreda ali eno metodo. V tem primeru gre za metodo razreda - natančneje, metode razreda so singleton metode, vendar singleton metode razreda, medtem ko so druge singleton metode predmeta.
Po drugi strani, class_eval
ovrednoti kodo v kontekstu razreda namesto primerka. Praktično ponovno odpre razred. Evo, kako class_eval
se lahko uporablja za ustvarjanje metode primerka:
Developer.class_eval do p 'class_eval - self is: ' + self.to_s def frontend p 'inside a method self is: ' + self.to_s end end # 'class_eval - self is: Developer' p developer = Developer.new # # developer.frontend # 'inside a method self is: #'
Če povzamem, ko pokličete class_eval
, spremenite self
za sklicevanje na prvotni razred in ko pokličete instance_eval
, self
spremembe, ki se nanašajo na metaklase prvotnega razreda.
Še en del sestavljanke metaprogramiranja je method_missing
. Ko pokličete metodo na predmetu, Ruby najprej preide v razred in pobrska po metodah primerka. Če metode tam ne najde, nadaljuje iskanje po verigi prednikov. Če Ruby metode še vedno ne najde, pokliče drugo metodo z imenom method_missing
ki je metoda primera Kernel
ki ga podeduje vsak predmet. Ker smo prepričani, da bo Ruby sčasoma poklical to metodo zaradi manjkajočih metod, jo lahko uporabimo za izvajanje nekaterih trikov.
define_method
je metoda, definirana v Module
razreda, ki ga lahko uporabite za dinamično ustvarjanje metod. Če želite uporabiti define_method
, ga pokličete z imenom nove metode in blokom, kjer parametri bloka postanejo parametri nove metode. Kakšna je razlika med uporabo def
ustvariti metodo in define_method
? Ni velike razlike, razen če lahko uporabite define_method
v kombinaciji z method_missing
napisati SUHO kodo. Natančneje, lahko uporabite define_method
namesto def
manipulirati z obsegi pri opredeljevanju razreda, vendar je to povsem druga zgodba. Oglejmo si preprost primer:
class Developer define_method :frontend do |*my_arg| my_arg.inject(1, :*) end class < 100 p Developer.backend # undefined method 'backend' for Developer:Class (NoMethodError) Developer.create_backend p Developer.backend # 'Born from the ashes!'
To kaže, kako define_method
je bil uporabljen za ustvarjanje metode primerka brez uporabe def
. Vendar lahko z njimi naredimo še veliko več. Oglejmo si ta delček kode:
class Developer def coding_frontend p 'writing frontend' end def coding_backend p 'writing backend' end end developer = Developer.new developer.coding_frontend # 'writing frontend' developer.coding_backend # 'writing backend'
Ta koda ni SUHA, ampak uporablja define_method
lahko ga naredimo SUHEGA:
class Developer ['frontend', 'backend'].each do |method| define_method 'coding_#{method}' do p 'writing ' + method.to_s end end end developer = Developer.new developer.coding_frontend # 'writing frontend' developer.coding_backend # 'writing backend'
To je veliko bolje, vendar še vedno ni popolno. Zakaj? Če želimo dodati novo metodo coding_debug
na primer, moramo postaviti to 'debug'
v matriko. Ampak z uporabo method_missing
to lahko popravimo:
class Developer def method_missing method, *args, &block return super method, *args, &block unless method.to_s =~ /^coding_w+/ self.class.send(:define_method, method) do p 'writing ' + method.to_s.gsub(/^coding_/, '').to_s end self.send method, *args, &block end end developer = Developer.new developer.coding_frontend developer.coding_backend developer.coding_debug
Ta del kode je nekoliko zapleten, zato ga razčlenimo. Poklic metode, ki ne obstaja, se bo sprožil method_missing
Tu želimo ustvariti novo metodo šele, ko se ime metode začne z 'coding_'
. V nasprotnem primeru pokličemo samo super, da poroča o metodi, ki dejansko manjka. In mi preprosto uporabljamo define_method
ustvariti to novo metodo. To je to! S tem delom kode lahko ustvarimo dobesedno na tisoče novih metod, začenši z 'coding_'
, in to je tisto, zaradi česar je naša koda SUHA. Ker define_method
se zgodi, da je zasebno za Module
, moramo uporabiti send
da ga prikličete.
To je le vrh ledene gore. Postati a Ruby Jedi , to je izhodišče. Ko obvladate te gradnike metaprogramiranja in resnično razumete njegovo bistvo, lahko nadaljujete do nečesa bolj zapletenega, na primer ustvarite svojega Domain-specific Language (DSL). DSL je tema sama po sebi, vendar so ti osnovni koncepti predpogoj za razumevanje naprednih tem. Nekateri najpogosteje uporabljeni dragulji v Tirnice so bili zgrajeni na ta način in ste verjetno uporabili njegov DSL, ne da bi ga sploh vedeli, na primer RSpec in ActiveRecord.
Upamo, da boste s tem člankom prišli korak bližje k razumevanju metaprogramiranja in morda celo k izdelavi lastnega DSL-ja, ki ga lahko uporabite za učinkovitejše kodiranje.