extend self in ruby

Seuraava koodi testattiin ruby 1.9.3 .

Luokka on tarkoitettu sekä datalle että käyttäytymiselle

Katsotaanpa tätä ruby-koodia.

1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8

Tässä meillä on Util luokka. Mutta huomaa, että kaikki tämän luokan metodit ovat luokan metodeja. Tällä luokalla ei ole yhtään instanssimuuttujaa. Yleensä luokkaa käytetään kantamaan sekä dataa että käyttäytymistä ja ,tässä tapauksessa Util-luokalla on vain käyttäytymistä eikä dataa.

Similar utility tools in ruby

Nyt saadaksemme perspektiiviä tähän keskusteluun katsotaanpa joitakin ruby-metodeja, jotka tekevät samanlaisia asioita. Tässä muutama.

1require 'base64'2Base64.encode64('hello world') #=>"aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2

Kaikissa edellä mainituissa tapauksissa luokan metodia kutsutaan luomatta ensin instanssia. Tämä on siis samanlainen tapa kuin käytin Util.double .

Katsotaanpa kuitenkin, mikä on kaikkien näiden objektien luokka.

1Base64.luokka #=> Module2Benchmark.luokka #=> Module3FileUtils.luokka #=> Module4Math.luokka #=> Moduuli

Näissä ei siis ole luokkia vaan moduuleja. Tämä herättää kysymyksen, miksi ruby-coren fiksut tyypit toteuttivat ne moduuleina sen sijaan, että olisivat luoneet luokan, kuten minä tein Utilille.

Syy on se, että Class on liian raskas vain metodien luomiseen, kuten double. Kuten aiemmin keskustelimme, luokalla on tarkoitus olla sekä dataa että käyttäytymistä. Jos ainoa asia, josta välität, on käyttäytyminen, ruby ehdottaa sen toteuttamista moduulina.

extend self is the answer

Ennen kuin jatkan keskustelua extend self tässä on, miltä Util-luokkani näyttää siirryttyäni Class:stä Module:een.

1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8

Miten siis extend self toimii

Katsotaan ensin mitä extend tekee.

1moduuli M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)

Yllä olevassa tapauksessa Calculator laajentaa moduulia M ja siten kaikki moduulin M instanssimetodit ovat suoraan Calculator:n käytettävissä.

Tässä tapauksessa Calculator on luokka, joka on laajentanut moduulia M. Calculator ei kuitenkaan tarvitse olla luokka laajentaakseen moduulia.

Kokeillaan nyt muunnelmaa, jossa Calculator on moduuli.

1moduuli M2 def double(i)3 i * 24 end5end67moduuli Calculator8 extend M9end10tulokset Calculator.double(4) #=> 8

Tässä Calculator on moduuli, joka laajentaa toista moduulia.

Nyt kun ymmärrämme, että moduuli voi laajentaa toista moduulia, tarkastellaan yllä olevaa koodia ja kysytään, miksi moduulia M edes tarvitaan. Miksi emme voi siirtää metodia double suoraan moduuliin Calculator. Kokeillaan sitä.

1moduuli Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8tulokset Calculator.double(4) #=> 8

Pääsin eroon moduulista M ja siirsin metodin double moduulin Calculator sisään. Koska moduuli M on poissa, muutin moduulista extend M moduuliin extend Calculator.

Yksi viimeinen korjaus.

Moduulin Calculator sisällä mikä on self. self on itse moduuli Calculator. Ei siis tarvitse toistaa Calculator kahteen kertaan. Tässä on lopullinen versio

1moduuli Calculator2 extend self34 def double(i)5 i * 26 end7end8tulokset Calculator.double(4) #=> 8

Luokan muuntaminen moduuliksi

Joka kerta kun törmäisin extend self kaltaiseen koodiin aivoni pysähtyvät hetkeksi. Sitten googlaisin sitä. Lukisin siitä. Kolme kuukautta myöhemmin toistan koko prosessin.

Paras tapa oppia se on käyttää sitä. Niinpä aloin etsiä käyttötapausta extend self. Ei ole hyvä käytäntö lähteä metsästämään koodia soveltamaan ideaa, joka sinulla on mielessäsi, mutta tässä yritin oppia.

Tässä on ennen tilannekuva metodeista Util-luokasta, joita käytin eräässä projektissa.

1class Util2 def self.config2hash(file); end3 def self.in_cents(määrä); end4 def self.localhost2public_url(url, protocol); end5end

Käytön jälkeen extend self koodista tuli

1module Util2 extend self34 def config2hash(tiedosto); end5 def in_cents(määrä); end6 def localhost2public_url(url, protocol); end7end

Paljon parempi. Se tekee tarkoituksen selväksi ja ,uskon, että se on linjassa sen kanssa, miten ruby odottaisi meidän käyttävän.

Toinen käyttö linjassa sen kanssa, miten Rails käyttää extend selfia

Tässä rakennan verkkokauppasovellusta ja jokaisen uuden tilauksen on saatava uusi tilausnumero kolmannen osapuolen myyntisovelluksesta. Koodi voisi näyttää tältä. Olen jättänyt metodien toteutuksen pois, koska niillä ei ole merkitystä tämän keskustelun kannalta.

1class Order2 def amount; end3 def buyer; end4 def shipped_at; end5 def number6 @number || self.class.next_order_number7 end89 def self.next_order_number; 'A100'; end10end111212puts Order.new.number #=> A100

Tässä metodi next_order_number saattaa tehdä monimutkaisen kutsun toiseen myyntijärjestelmään. Ihannetapauksessa luokan Order ei pitäisi paljastaa metodia next_order_number . Voimme siis tehdä metodista private, mutta se ei ratkaise perusongelmaa. Ongelma on se, että mallin Order ei pitäisi tietää, miten uusi tilausnumero luodaan. No voimme siirtää metodin next_order_number toiseen Util-luokkaan, mutta se loisi liikaa etäisyyttä.

Tässä on ratkaisu käyttäen extend self.

1module Checkout2 extend self34 def next_order_number; 'A100'; end56 class Order7 def amount; end8 def buyer; end9 def shipped_at; end10 def number11 @number || Checkout.next_order_number12 end13 end14end1516tulosteet Checkout::Order.new.number #=> A100

Paljon parempi. Luokka Order ei paljasta metodia next_order_number ja tämä metodi on aivan samassa tiedostossa. Ei tarvitse avata luokkaa Util.

Articles

Vastaa

Sähköpostiosoitettasi ei julkaista.