extend self in ruby
Az alábbi kódot ruby 1.9.3-mal teszteltük.
Az osztály az adatokra és a viselkedésre is vonatkozik
Nézzük meg ezt a ruby kódot.
1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Itt van egy Util
osztály. De figyeljük meg, hogy ennek az osztálynak minden metódusa osztály metódus. Ennek az osztálynak nincsenek példányváltozói. Általában egy osztályt arra használnak, hogy adatokat és viselkedést is hordozzon, és ,ebben az esetben az Util osztály csak viselkedéssel rendelkezik és adatokkal nem.
Hasonló segédeszközök ruby-ban
Most, hogy egy kis rálátást kapjunk erre a vitára, nézzünk meg néhány ruby metódust, amelyek hasonló dolgot csinálnak. Íme néhány.
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
A fenti esetekben az osztály metódusa előbbi példány létrehozása nélkül kerül meghívásra. Ez tehát hasonló az általam használt Util.double
.
Mégis nézzük meg, hogy mi az osztálya ezeknek az objektumoknak.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Ezek tehát nem osztályok, hanem modulok. Ez felveti a kérdést, hogy az okos srácok a ruby-core-nál miért implementálták őket modulokként, ahelyett, hogy egy osztályt hoztak volna létre, ahogy én tettem az Util esetében.
Az ok az, hogy a Class túl nehézkes ahhoz, hogy csak metódusokat hozzunk létre, mint double
. Ahogy korábban megbeszéltük, egy osztálynak adatot és viselkedést is kell tartalmaznia. Ha csak a viselkedés érdekel, akkor a ruby azt javasolja, hogy modulként valósítsuk meg.
extend self is the answer
Mielőtt rátérnék a extend self
tárgyalására, íme, hogyan fog kinézni a Util
osztályom, miután a Class
-ből Module
-ba költöztem.
1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Szóval hogyan működik az extend self
Először is nézzük meg, mit csinál az extend.
1modul M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)
A fenti esetben Calculator
kiterjeszti a M
modult, és így a M
modul összes példány metódusa közvetlenül elérhető Calculator
számára.
Ez esetben Calculator
egy osztály, amely kiterjesztette a M
modult. Azonban Calculator
nem kell, hogy osztály legyen egy modul kiterjesztéséhez.
Most próbáljunk meg egy olyan variációt, ahol Calculator
egy modul.
1module M2 def double(i)3 i * 24 end5end67module Calculator8 extend M9end10puts Calculator.double(4) #=> 8
Itt a Calculator egy modul, amely egy másik modult bővít.
Most, hogy megértettük, hogy egy modul bővíthet egy másik modult, nézzük meg a fenti kódot, és kérdezzük meg, miért van egyáltalán szükség a M
modulra. Miért nem tudjuk a double
metódust közvetlenül a Calculator modulba áthelyezni. Próbáljuk ki.
1modul Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Megszabadultam a M
modultól, és a double
metódust a Calculator
modulba költöztettem. Mivel a M
modul eltűnt, a extend M
modulból átváltottam a extend Calculator
-re.
Egy utolsó javítás.
A Calculator modulon belül mi a self
. self
az maga a Calculator
modul. Tehát nincs szükség a Calculator
kétszeri megismétlésére. Itt a végleges változat
1modul Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Az osztály átalakítása modullá
Minden alkalommal, amikor olyan kóddal találkoznék, mint a extend self
, az agyam megáll egy pillanatra. Aztán rákeresnék a Google-ben. Utánaolvasok. Három hónappal később megismétlem az egész folyamatot.
A legjobb módja a tanulásnak, ha használod. Tehát elkezdtem keresni egy esetet, hogy használjam extend self
. Nem jó gyakorlat kódot vadászni egy ötlet alkalmazásához, ami a fejedben van, de itt próbáltam tanulni.
Itt van egy korábbi pillanatkép a Util
osztály metódusairól, amit egy projektben használtam.
1class Util2 def self.config2hash(file); end3 def self.in_cents(összeg); end4 def self.localhost2public_url(url, protocol); end5end
Az extend self
kód használata után
1module Util2 extend self34 def config2hash(file); end5 def in_cents(összeg); end6 def localhost2public_url(url, protocol); end7end
Sokkal jobb lett. Világossá teszi a szándékot, és ,azt hiszem, összhangban van azzal, ahogy a ruby elvárná, hogy használjuk.
Egy másik használat összhangban van azzal, ahogy a Rails használja az extend self-et
Itt egy e-kereskedelmi alkalmazást építek, és minden új rendelésnek új rendelési számot kell kapnia egy harmadik fél értékesítési alkalmazásából. A kód így nézhet ki. A metódusok implementációját kihagytam, mert azok nem relevánsak a vita szempontjából.
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
Itt a módszer next_order_number
egy másik értékesítési rendszer bonyolult hívását végezheti. Ideális esetben az Order
osztálynak nem kellene exponálnia a next_order_number
metódust . Tehát ezt a metódust private
-nek tehetjük, de ez nem oldja meg az alapvető problémát. A probléma az, hogy a Order
modellnek nem szabad tudnia, hogyan generálódik az új rendelésszám. Nos, áthelyezhetjük a next_order_number
metódust egy másik Util
osztályba, de ez túl nagy távolságot hozna létre.
Itt egy megoldás a extend self
használatával.
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 end14end1516puts Checkout::Order.new.number #=> A100
Sokkal jobb. Az Order osztály nem exponálja a next_order_number
metódust, és ez a metódus ott van ugyanabban a fájlban. Nem kell megnyitni a Util
osztályt.