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.

Articles

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.