extend self in ruby
Následující kód byl testován s ruby 1.9.3 .
Třída je určena pro data i chování
Podívejme se na tento kód v ruby.
1třída Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Tady máme třídu Util
. Všimněte si však, že všechny metody této třídy jsou metodami třídy. Tato třída nemá žádné instanční proměnné. Obvykle se třída používá k tomu, aby nesla jak data, tak chování, a ,v tomto případě má třída Util pouze chování a žádná data.
Podobné užitečné nástroje v ruby
Nyní se pro získání určitého pohledu na tuto diskusi podívejme na některé metody v ruby, které dělají podobné věci. Zde je jich několik.
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
Ve všech výše uvedených případech je metoda třídy vyvolána bez předchozího vytvoření instance. Je to tedy podobný způsob, jaký jsem použil Util.double
.
Podívejme se však, jaká je třída všech těchto objektů.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Nejsou to tedy třídy, ale moduly. To vyvolává otázku, proč je chytráci z ruby-core implementovali jako moduly, místo aby vytvořili třídu tak, jak jsem to udělal já pro Util.
Důvodem je, že třída je příliš těžká pro vytváření pouze metod jako double
. Jak jsme si řekli dříve, třída má mít jak data, tak chování. Pokud vás zajímá pouze chování, pak ruby navrhuje implementovat ho jako modul.
rozšířit self je řešení
Než přejdu k diskusi o extend self
, tady je, jak bude moje třída Util
vypadat po přechodu z Class
na Module
.
1modul Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Takže jak funguje extend self
Nejprve se podíváme, co dělá extend.
1modul M2 def double(i)3 i * 24 end5end67třída Calculator8 extend M9end10puts Calculator.double(4)
V uvedeném případě Calculator
rozšiřuje modul M
, a proto jsou všechny instanční metody modulu M
přímo dostupné Calculator
.
V tomto případě je Calculator
třída, která rozšířila modul M
. Nicméně Calculator
nemusí být třída, aby rozšiřovala modul.
Nyní zkusíme variantu, kdy Calculator
je modul.
1modul M2 def double(i)3 i * 24 end5end67modul Calculator8 extend M9end10puts Calculator.double(4) #=> 8
Tady Calculator je modul, který rozšiřuje jiný modul.
Teď, když jsme pochopili, že modul může rozšiřovat jiný modul, podívejme se na výše uvedený kód a položme si otázku, proč je modul M
vůbec potřeba. Proč nemůžeme metodu double
přesunout přímo do modulu Kalkulačka. Zkusme to.
1modul Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Zbavil jsem se modulu M
a metodu double
jsem přesunul dovnitř modulu Calculator
. Protože modul M
zmizel, přešel jsem z extend M
na extend Calculator
.
Ještě poslední oprava.
Vnitř modulu Kalkulačka co je self
. self
je samotný modul Calculator
. Není tedy nutné opakovat Calculator
dvakrát. Zde je konečná verze
1modul Calculator2 extend self34 def double(i)5 i * 26 end7end8výstupy Calculator.double(4) #=> 8
Převod třídy na modul
Pokud bych narazil na kód jako extend self
, můj mozek se na chvíli zastaví. Pak bych si ho vygooglil. Přečtu si o tom. O tři měsíce později celý proces zopakuji.
Nejlepší způsob, jak se to naučit, je používat to. Začal jsem tedy hledat případ, který bych mohl použít extend self
. Není dobrým zvykem lovit kód pro aplikaci myšlenky, kterou máte v hlavě, ale tady jsem se to snažil naučit.
Tady je předchozí snímek metod ze třídy Util
, které jsem použil v projektu.
1třída Util2 def self.config2hash(file); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end
Po použití extend self
se z kódu stal
1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end
Mnohem lepší. Dává to jasně najevo záměr a ,věřím, že je to v souladu se způsobem, který by od nás ruby očekávalo.
Další použití v souladu s tím, jak Rails používá extend self
Tady buduji aplikaci elektronického obchodu a každá nová objednávka potřebuje získat nové číslo objednávky z prodejní aplikace třetí strany. Kód by mohl vypadat takto. Vynechal jsem implementaci metod, protože nejsou pro tuto diskusi relevantní.
1třída 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'; end10end1112puts Order.new.number #=> A100
Zde by metoda next_order_number
mohla složitě volat jiný prodejní systém. V ideálním případě by třída Order
neměla vystavovat metodu next_order_number
. Můžeme tedy z této metody udělat metodu private
, ale to neřeší základní problém. Problém spočívá v tom, že model Order
by neměl vědět, jak se generuje nové číslo objednávky. Dobře, můžeme metodu next_order_number
přesunout do jiné třídy Util
, ale to by vytvořilo příliš velký odstup.
Zde je řešení pomocí 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 end14end1516puts Checkout::Order.new.number #=> A100
Mnohem lepší. Třída Order nevystavuje metodu next_order_number
a tato metoda je přímo v tomtéž souboru. Není třeba otevírat třídu Util
.