extend self in ruby
Codul de mai jos a fost testat cu ruby 1.9.3 .
Clasa este destinată atât pentru date cât și pentru comportament
Să ne uităm la acest cod ruby.
1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Aici avem o clasă Util
. Dar observați că toate metodele din această clasă sunt metode de clasă. Această clasă nu are nicio variabilă de instanță. De obicei, o clasă este folosită pentru a transporta atât date cât și comportament și ,în acest caz, clasa Util are doar comportament și nu are date.
Instrumente utilitare similare în ruby
Acum, pentru a avea o perspectivă asupra acestei discuții, să ne uităm la câteva metode ruby care fac lucruri similare. Iată câteva.
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
În toate cazurile de mai sus, metoda clasei este invocată fără a crea mai întâi o instanță. Deci, acest lucru este similar cu modul în care am folosit Util.double
.
Să vedem însă care este clasa tuturor acestor obiecte.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Atunci acestea nu sunt clase, ci module. Asta ridică întrebarea de ce băieții deștepți de la ruby-core le-au implementat ca module în loc să creeze o clasă așa cum am făcut eu pentru Util.
Motivul este că Class este prea grea pentru a crea doar metode ca double
. După cum am discutat mai devreme, o clasă ar trebui să aibă atât date cât și comportament. Dacă singurul lucru care vă interesează este comportamentul, atunci ruby sugerează să îl implementați ca un modul.
Extend self este răspunsul
Înainte de a discuta despre extend self
iată cum va arăta clasa mea Util
după trecerea de la Class
la Module
.
1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Atunci cum funcționează extend self
În primul rând să vedem ce face extend.
1modul M2 def double(i)3 i * 24 end5end67clasa Calculator8 extend M9end10surse Calculator.double(4)
În cazul de mai sus, Calculator
extinde modulul M
și, prin urmare, toate metodele de instanță ale modulului M
sunt direct disponibile pentru Calculator
.
În acest caz, Calculator
este o clasă care a extins modulul M
. Cu toate acestea, Calculator
nu trebuie să fie o clasă pentru a extinde un modul.
Acum să încercăm o variantă în care Calculator
este un modul.
1modul M2 def double(i)3 i * 24 end5end67modul Calculator8 extend M9end10put Calculator.double(4) #=> 8
Aici Calculator este un modul care extinde un alt modul.
Acum că am înțeles că un modul poate extinde un alt modul, priviți codul de mai sus și întrebați-vă de ce este nevoie de modulul M
. De ce nu putem muta metoda double
direct în modulul Calculator. Să încercăm asta.
1module Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Am scăpat de modulul M
și am mutat metoda double
în interiorul modulului Calculator
. Din moment ce modulul M
a dispărut am trecut de la extend M
la extend Calculator
.
O ultimă remediere.
În interiorul modulului Calculator ce este self
. este chiar modulul Calculator
. Deci nu este nevoie să se repete Calculator
de două ori. Iată versiunea finală
1module Calculator2 extend self34 def double(i)5 i * 26 end7end8put Calculator.double(4) #=> 8
Conversia unei clase într-un modul
De fiecare dată când aș întâlni un cod ca extend self
creierul meu se oprește pentru un moment. Apoi l-aș căuta pe Google. Voi citi despre el. Trei luni mai târziu voi repeta întregul proces.
Cel mai bun mod de a învăța este să îl folosești. Așa că am început să caut un caz în care să folosesc extend self
. Nu este o practică bună să mergi la vânătoare de cod pentru a aplica o idee pe care o ai în minte, dar aici încercam să învăț.
Iată un instantaneu before al metodelor din clasa Util
pe care le-am folosit într-un proiect.
1class Util2 def self.config2hash(file); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end
După ce am folosit extend self
codul a devenit
1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end
Mult mai bine. Face ca intenția să fie clară și ,cred, este în concordanță cu modul în care ruby s-ar aștepta să folosim.
O altă utilizare în concordanță cu modul în care Rails folosește extend self
Aici construiesc o aplicație de comerț electronic și fiecare comandă nouă trebuie să primească un nou număr de comandă de la o aplicație de vânzări terță parte. Codul ar putea arăta în felul următor. Am omis implementarea metodelor pentru că nu sunt relevante pentru această discuție.
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'; end10end1112put Order.new.number #=> A100
Aici, metoda next_order_number
ar putea face un apel complicat către un alt sistem de vânzări. În mod ideal, clasa Order
nu ar trebui să expună metoda next_order_number
. Așadar, putem face această metodă private
, dar acest lucru nu rezolvă problema de fond. Problema este că modelul Order
nu ar trebui să știe cum este generat noul număr de comandă. Ei bine, putem muta metoda next_order_number
într-o altă clasă Util
, dar asta ar crea o distanță prea mare.
Iată o soluție folosind 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 end14end1516pune Checkout::Order.new.number #=> A100
Mult mai bine. Clasa Order nu expune metoda next_order_number
, iar această metodă este chiar acolo, în același fișier. Nu este nevoie să deschidem clasa Util
.
.