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.

.

Articles

Lasă un răspuns

Adresa ta de email nu va fi publicată.