extend self in ruby

Följande kod testades med ruby 1.9.3 .

Klassen är avsedd för både data och beteende

Låt oss titta på denna ruby-kod.

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

Här har vi en Util klass. Men lägg märke till att alla metoder i den här klassen är klassmetoder. Den här klassen har inga instansvariabler. Vanligtvis används en klass för att bära både data och beteende och ,i det här fallet har Util-klassen bara beteende och inga data.

Samma verktyg i ruby

För att få lite perspektiv på den här diskussionen kan vi nu titta på några ruby-metoder som gör liknande saker. Här är några.

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

I alla ovanstående fall anropas klassmetoden utan att först skapa en instans. Detta liknar alltså det sätt som jag använde Util.double .

Men låt oss se vilken klass alla dessa objekt har.

1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module

Dessa är alltså inte klasser utan moduler. Det väcker frågan varför de smarta killarna på ruby-core implementerade dem som moduler istället för att skapa en klass som jag gjorde för Util.

Skälet är att Class är för tungt för att skapa bara metoder som double. Som vi diskuterade tidigare ska en klass ha både data och beteende. Om det enda du bryr dig om är beteende föreslår ruby att du implementerar det som en modul.

extend self är svaret

Innan jag fortsätter att diskutera extend self ser min Util-klass ut så här efter att ha gått från Class till Module.

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

Så hur fungerar extend self

Först ska vi se vad extend gör.

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

I ovanstående fall förlänger Calculator modulen M och därmed är alla instansmetoderna i modulen M direkt tillgängliga för Calculator.

I det här fallet är Calculator en klass som förlängt modulen M. Calculator behöver dock inte vara en klass för att förlänga en modul.

Nu kan vi prova en variant där Calculator är en modul.

1modul M2 def double(i)3 i * 24 end5end67modul Calculator8 extend M9end10puts Calculator.double(4) #=> 8

Här är Calculator en modul som förlänger en annan modul.

Nu när vi förstår att en modul kan förlänga en annan modul kan vi titta på koden ovan och fråga oss varför modul M ens behövs. Varför kan vi inte flytta metoden double direkt till modulen Calculator. Låt oss prova det.

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

Jag gjorde mig av med modul M och flyttade metoden double inuti modul Calculator. Eftersom modul M är borta ändrade jag från extend M till extend Calculator.

En sista fix.

Inuti modulen Calculator vad är self. self är själva modulen Calculator. Det finns alltså ingen anledning att upprepa Calculator två gånger. Här är den slutliga versionen

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

Konvertering av en klass till en modul

Varje gång jag stöter på en kod som extend self stannar min hjärna upp en stund. Sedan googlar jag efter den. Läser om den. Tre månader senare upprepar jag hela processen.

Det bästa sättet att lära sig det är att använda det. Så jag började leta efter ett fall att använda extend self. Det är inte bra att gå på jakt efter kod för att tillämpa en idé du har i huvudet, men här försökte jag lära mig.

Här är en före ögonblicksbild av metoder från Util-klassen som jag använde i ett projekt.

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

Efter att ha använt extend self blev koden

1modul Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end

Mycket bättre. Det gör avsikten tydlig och ,tror jag, är i linje med hur ruby skulle förvänta sig att vi använder.

En annan användning i linje med hur Rails använder extend self

Här bygger jag en e-handelsapplikation och varje ny beställning behöver få ett nytt ordernummer från en tredjepartsförsäljningsapplikation. Koden kan se ut så här. Jag har utelämnat implementeringen av metoderna eftersom de inte är relevanta för den här diskussionen.

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

Här kan metoden next_order_number göra ett komplicerat samtal till ett annat försäljningssystem. Helst borde klassen Order inte exponera metod next_order_number . Så vi kan göra denna metod private men det löser inte grundproblemet. Problemet är att modell Order inte ska veta hur det nya ordernumret genereras. Nåväl, vi kan flytta metoden next_order_number till en annan Util-klass men det skulle skapa för mycket avstånd.

Här är en lösning med hjälp av extend self.

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

Mycket bättre. Klassen Order exponerar inte metod next_order_number och denna metod finns i samma fil. Du behöver inte öppna Util-klassen.

Articles

Lämna ett svar

Din e-postadress kommer inte publiceras.