estendere sé in rubino

Il seguente codice è stato testato con rubino 1.9.3.

La classe è intesa sia per i dati che per il comportamento

Guardiamo questo codice ruby.

1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8

Qui abbiamo una classe Util. Ma notate che tutti i metodi di questa classe sono metodi di classe. Questa classe non ha alcuna variabile di istanza. Di solito una classe è usata per trasportare sia dati che comportamenti e, in questo caso, la classe Util ha solo comportamenti e nessun dato.

Simili strumenti di utilità in rubino

Ora per avere una prospettiva su questa discussione guardiamo alcuni metodi di rubino che fanno cose simili. Eccone alcuni.

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

In tutti i casi precedenti il metodo della classe viene invocato senza creare prima un’istanza. Quindi questo è simile al modo in cui ho usato Util.double .

Vediamo però qual è la classe di tutti questi oggetti.

1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module

Quindi queste non sono classi ma moduli. Questo porta alla domanda perché i ragazzi intelligenti di ruby-core li hanno implementati come moduli invece di creare una classe come ho fatto per Util.

La ragione è che Class è troppo pesante per creare solo metodi come double. Come abbiamo discusso prima una classe dovrebbe avere sia dati che comportamento. Se l’unica cosa che ti interessa è il comportamento, allora ruby suggerisce di implementarlo come modulo.

extend self è la risposta

Prima di continuare a discutere extend self ecco come sarà la mia classe Util dopo essere passata da Class a Module.

1modulo Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8

Come funziona extend self

Prima vediamo cosa fa extend.

1modulo M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)

Nel caso precedente Calculator sta estendendo il modulo M e quindi tutti i metodi di istanza del modulo M sono direttamente disponibili per Calculator.

In questo caso Calculator è una classe che estende il modulo M. Tuttavia Calculator non deve essere una classe per estendere un modulo.

Ora proviamo una variazione dove Calculator è un modulo.

1modulo M2 def double(i)3 i * 24 end5end67modulo Calculator8 extend M9end10puts Calculator.double(4) #=> 8

Qui Calculator è un modulo che sta estendendo un altro modulo.

Ora che abbiamo capito che un modulo può estendere un altro modulo guardiamo il codice sopra e chiediamoci perché il modulo M è necessario. Perché non possiamo spostare il metodo double nel modulo Calculator direttamente. Proviamo.

1modulo Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8

Mi sono liberato del modulo M e ho spostato il metodo double nel modulo Calculator. Dato che il modulo M non c’è più, ho cambiato da extend M a extend Calculator.

Un’ultima correzione.

Dentro il modulo Calcolatrice cos’è self. self è il modulo Calculator stesso. Quindi non c’è bisogno di ripetere Calculator due volte. Ecco la versione finale

1modulo Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8

Convertire una classe in un modulo

Ogni volta che incontro del codice come extend self il mio cervello si ferma un attimo. Poi lo cerco su Google. Leggo qualcosa al riguardo. Tre mesi dopo ripeterei l’intero processo.

Il modo migliore per imparare è usarlo. Così ho iniziato a cercare un caso da usare extend self. Non è una buona pratica andare a caccia di codice per applicare un’idea che hai in mente, ma qui stavo cercando di imparare.

Ecco un’istantanea dei metodi della classe Util che ho usato in un progetto.

1class Util2 def self.config2hash(file); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end

Dopo aver usato extend self il codice è diventato

1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end

Molto meglio. Rende chiaro l’intento e, credo, è in linea con il modo in cui Ruby si aspetta che noi usiamo.

Un altro uso in linea con il modo in cui Rails usa extend self

Qui sto costruendo un’applicazione ecommerce e ogni nuovo ordine deve ottenere un nuovo numero d’ordine da un’applicazione di vendita di terze parti. Il codice potrebbe assomigliare a questo. Ho omesso l’implementazione dei metodi perché non sono rilevanti per questa discussione.

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'; end10end1112puts Order.new.number #=> A100

Qui il metodo next_order_number potrebbe fare una chiamata complicata ad un altro sistema di vendita. Idealmente la classe Order non dovrebbe esporre il metodo next_order_number. Quindi possiamo rendere questo metodo private ma questo non risolve il problema alla radice. Il problema è che il modello Order non dovrebbe sapere come viene generato il nuovo numero d’ordine. Possiamo spostare il metodo next_order_number in un’altra classe Util ma questo creerebbe troppa distanza.

Ecco una soluzione usando extend self.

1modulo 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

Molto meglio. La classe Order non espone il metodo next_order_number e questo metodo è proprio lì nello stesso file. Non c’è bisogno di aprire la classe Util.

Articles

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.