extend self in ruby
Følgende kode blev testet med ruby 1.9.3 .
Klassen er beregnet til både data og adfærd
Lad os se på denne ruby-kode.
1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Her har vi en Util
-klasse. Men bemærk, at alle metoderne på denne klasse er klassemetoder. Denne klasse har ikke nogen instansvariabler. Normalt bruges en klasse til at bære både data og adfærd, og ,i dette tilfælde har Util-klassen kun adfærd og ingen data.
Sammenlignende hjælpeværktøjer i ruby
Nu for at få noget perspektiv på denne diskussion lad os se på nogle ruby-metoder, der gør lignende ting. Her er et par stykker.
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 alle ovenstående tilfælde påberåbes klassemetoden uden at der først oprettes en instans. Så dette svarer til den måde, jeg brugte Util.double
.
Lader os imidlertid se, hvad der er klassen for alle disse objekter.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Så det er ikke klasser, men moduler. Det rejser spørgsmålet, hvorfor de kloge fyre på ruby-core implementerede dem som moduler i stedet for at oprette en klasse, som jeg gjorde for Util.
Ræsonen er, at Class er for tungt til kun at oprette metoder som double
. Som vi diskuterede tidligere, er det meningen, at en klasse skal have både data og adfærd. Hvis det eneste, du bekymrer dig om, er adfærd, foreslår ruby at implementere det som et modul.
extend self er svaret
Hvor jeg går videre til at diskutere extend self
er her, hvordan min Util
-klasse vil se ud efter at være flyttet fra Class
til Module
.
1modul Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Så hvordan virker extend self
Først skal vi se, hvad extend gør.
1modul M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)
I ovenstående tilfælde udvider Calculator
modul M
, og derfor er alle instansmetoderne i modul M
direkte tilgængelige for Calculator
.
I dette tilfælde er Calculator
en klasse, der har udvidet modul M
. Men Calculator
behøver ikke at være en klasse for at udvide et modul.
Nu kan vi prøve en variant, hvor Calculator
er et modul.
1modul M2 def double(i)3 i * 24 end5end67modul Calculator8 extend M9end10puts Calculator.double(4) #=> 8
Her er Calculator et modul, der udvider et andet modul.
Nu da vi har forstået, at et modul kan udvide et andet modul, kan vi se på ovenstående kode og stille spørgsmålstegn ved, hvorfor modul M
overhovedet er nødvendigt. Hvorfor kan vi ikke flytte metoden double
direkte til modulet Calculator. Lad os prøve det.
1modul Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Jeg er sluppet af med modul M
og har flyttet metoden double
inde i modul Calculator
. Da modul M
er væk, ændrede jeg fra extend M
til extend Calculator
.
En sidste rettelse.
Inden for modulet Calculator hvad er self
. er selve modulet Calculator
. Så der er ingen grund til at gentage Calculator
to gange. Her er den endelige version
1modul Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Konvertering af en klasse til et modul
Hver gang jeg støder på kode som extend self
vil min hjerne holde en pause et øjeblik. Så ville jeg google efter det. Vil læse om det. Tre måneder senere vil jeg gentage hele processen.
Den bedste måde at lære det på er ved at bruge det. Så jeg begyndte at lede efter en sag at bruge extend self
. Det er ikke en god praksis at gå på jagt efter kode for at anvende en idé, du har i hovedet, men her forsøgte jeg at lære.
Her er et før-snapshot af metoder fra Util
-klassen, som jeg brugte i et projekt.
1class Util2 def self.config2hash(file); end3 def self.in_cents(beløb); end4 def self.localhost2public_url(url, protocol); end5end
Efter brug af extend self
blev koden
1modul Util2 extend self34 def config2hash(file); end5 def in_cents(beløb); end6 def localhost2public_url(url, protocol); end7end
Meget bedre. Det gør hensigten klar, og ,jeg tror, det er i overensstemmelse med den måde ruby ville forvente, at vi bruger.
En anden brug i overensstemmelse med hvordan Rails bruger extend self
Her er jeg ved at bygge en e-handelsapplikation, og hver ny ordre skal få et nyt ordrenummer fra en tredjepartssalgsapplikation. Koden kunne se således ud. Jeg har udeladt implementeringen af metoderne, fordi de ikke er relevante for denne diskussion.
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
Her kan metoden next_order_number
foretage et kompliceret opkald til et andet salgssystem. Ideelt set bør klassen Order
ikke eksponere metode next_order_number
. Så vi kan gøre denne metode private
, men det løser ikke det grundlæggende problem. Problemet er, at model Order
ikke skal vide, hvordan det nye ordrenummer genereres. Godt nok kan vi flytte metoden next_order_number
til en anden Util
-klasse, men det ville skabe for stor afstand.
Her er en løsning ved hjælp af 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
Meget bedre. Klassen Order eksponerer ikke metode next_order_number
, og denne metode er lige der i samme fil. Det er ikke nødvendigt at åbne Util
-klassen.