Immaginate di aver bisogno di un modo per controllare il valore di un oggetto, ed eseguire un’azione diversa in base a questo. Il modo di programmazione orientata agli oggetti è quello di usare il polimorfismo, e vedremo come farlo tra un secondo.
Ma prima, vediamo come funziona l’espressione case
, e come si può usare per raggiungere l’obiettivo di cui sopra.
Come funziona il caso?
Ci sono due parti principali nell’espressione case
. La clausola case
e la clausola when
.
La clausola when
è l’espressione che definisce il destinatario dell’operatore (di default ===
), e la clausola case
definisce l’argomento passato ad esso.
Vediamo un esempio.
case awhen String #..end
Quindi potete pensare all’esempio precedente come String === a
, o String.===(a)
.
Infine, c’è anche una clausola else
che potete usare per quando non c’è corrispondenza.
case my_objectwhen String "This is a string"else "I have no idea what this is"end
L’operatore triplo uguale (===)
Di default, case
usa l’operatore triplo uguale (===
) per fare il confronto.
La cosa con ===
è che non ha nulla a che fare con l’uguaglianza. Per impostazione predefinita, è un alias dell’operatore doppio equals (==
) che normalmente controlla se due oggetti hanno un valore equivalente, ma può essere definito per significare qualsiasi cosa.
Per esempio, un range lo definisce come alias per includes?
, una regex lo definisce come alias per match
, una classe per is_a?
, un proc per call
. Avete capito bene.
Quell’operatore funziona come previsto con i letterali, ma non con le classi:
1 === 1 # => trueNumeric === Numeric # => false
Questo perché il ===
è un alias per kind_of?
. Ed ecco la documentazione per kind_of?
.
kind_of?(class) → true o false
Ritorna true se class è la classe di obj, o se class è una delle superclassi di obj o dei moduli inclusi in obj.
Questo significa che se volete fare un case ... when
sulla classe di un oggetto, questo non funzionerà:
obj = 'Hi'case obj.classwhen String "It's a string"end
Questo perché, si traduce in String === String
. E questo restituisce false
.
L’operatore predefinito
L’espressione case
ha un operatore predefinito. E questo significa che non devi specificarlo. Questo è molto bello, perché puoi semplicemente scrivere when String
e l’operatore predefinito è ===
.
Ma ci sono momenti in cui vuoi usare un operatore diverso. E Ruby vi permette di farlo. Ecco come fare.
casewhen a < 3 "Smaller than 3"end
Case è un’espressione
Proprio come dice il nome, il tutto è un’espressione. Ciò significa che valuta un valore. Quindi potete assegnare il risultato dell’intera case
espressione ad una variabile, così.
value = case when a < 3 "Smaller than 3" end
Non c’è nessun fall-through
Se vieni da altri linguaggi, probabilmente hai usato un’istruzione switch
o qualcosa di simile, e avrete notato che case
non usa parole chiave come break
per interrompere il flusso.
Questo perché non c’è nessun fall-through con case
. Restituisce semplicemente il valore dell’espressione che è stata abbinata e basta.
Combinazioni multiple
Fino ad ora, hai usato solo un valore per la clausola when
. Ma puoi usare valori multipli.
case awhen 1..3 "Small number"end
Corrispondenza delle regex
Puoi usare le espressioni case
per far corrispondere qualsiasi cosa. Ma nel caso in cui non sia ovvio, potete anche abbinare delle regex, o dei lambda. Ecco un esempio.
even = ->(x) { x % 2 == 0 }case awhen even "It's even"when /^+$/ "It's an integer"end
Definisci il tuo
Uno dei fatti meno conosciuti è che puoi definire i tuoi comparatori.
Text = Struct.new(:min_length) do def ===(string) string.size > min_length && string.is_a?(String) endendcase awhen Text.new(100) "It's text"end
Nell’esempio precedente, se a
è una stringa di più di 100 caratteri, allora l’espressione case
restituirà It's text
.
Utilizzate il polimorfismo invece
Non mi addentrerò qui nei benefici dell’OOP, potete leggere di più in merito nell’articolo Programmazione orientata agli oggetti con Ruby, ma vi mostrerò come potete usare il polimorfismo per sostituire un’espressione case
.
La versione del caso
class Person attr_reader :country def initialize(country) @country = country end def nationality case country when "USA" "This guy is an American" when "Romania" "This guy is a Romanian" end endendjohn = Person.new("USA")puts john.nationality # => This guy is an American
La versione OOP
class Person attr_reader :country def initialize(country) @country = country end def nationality country.nationality endendclass America def nationality "This guy is an American" endendclass Romania def nationality "This guy is a Romanian" endendjohn = Person.new(America.new)puts john.nationality # => This guy is an American
È una buona idea eliminare la maggior parte del codice di controllo del flusso (i.e. if
s, e case
espressioni) il più possibile. Migliora l’aderenza al principio di responsabilità unica e rende il programma più leggibile in generale.