cat swift/pattern-matching-dominio.md
Pattern matching come linguaggio del dominio
switch, if case, guard case e binding dei valori associati per trasformare enum ricchi in logica leggibile.
Quando inizi a usare enum con associated values, hai bisogno di un modo elegante per leggerli. In Swift questo modo è il pattern matching.
Il pattern matching non è solo “uno switch più potente”. È il modo in cui il codice interroga il dominio.
switch esaustivo
Partiamo da uno sconto:
enum Sconto {
case percentuale(Int)
case importoFisso(Int)
case spedizioneGratuita
}
Per applicarlo:
func descrivi(_ sconto: Sconto) -> String {
switch sconto {
case .percentuale(let valore):
return "\(valore)% di sconto"
case .importoFisso(let cent):
return "\(cent) centesimi di sconto"
case .spedizioneGratuita:
return "Spedizione gratuita"
}
}
Lo switch è esaustivo: se aggiungi un nuovo caso a Sconto, Swift ti costringerà ad aggiornare il codice. Questo è un vantaggio enorme nella manutenzione.
Binding dei valori
Questa riga:
case .percentuale(let valore):
estrae il valore associato al caso .percentuale.
Puoi anche scrivere:
case let .percentuale(valore):
La seconda forma è utile quando hai più valori:
enum Regola {
case intervallo(min: Int, max: Int)
}
switch regola {
case let .intervallo(min, max):
print("Da \(min) a \(max)")
}
where: condizioni dentro il pattern
Puoi aggiungere condizioni:
func valida(_ sconto: Sconto) -> Bool {
switch sconto {
case .percentuale(let valore) where valore > 0 && valore <= 100:
return true
case .importoFisso(let cent) where cent > 0:
return true
case .spedizioneGratuita:
return true
default:
return false
}
}
where va usato con misura. Se la validazione è parte dell’invariante del tipo, spesso è meglio impedirla già in fase di costruzione. Ma per regole applicative è molto utile.
if case
Quando ti interessa un solo caso:
let sconto = Sconto.spedizioneGratuita
if case .spedizioneGratuita = sconto {
print("Mostra badge spedizione gratuita")
}
Con valore associato:
if case .percentuale(let valore) = sconto {
print("Sconto percentuale: \(valore)")
}
Questo evita switch rumorosi quando vuoi controllare una sola alternativa.
guard case
guard case è utile quando una funzione può continuare solo per un certo stato.
enum StatoOrdine {
case creato
case pagato(transactionId: String)
case spedito(tracking: String)
}
func generaEtichettaSpedizione(per stato: StatoOrdine) -> String? {
guard case .pagato(let transactionId) = stato else {
return nil
}
return "Etichetta per pagamento \(transactionId)"
}
Il codice dichiara chiaramente il prerequisito.
Pattern matching su tuple
Puoi combinare più condizioni:
enum TipoUtente {
case nuovo
case ricorrente
}
func puoUsareCouponPrimoOrdine(tipo: TipoUtente, carrelloVuoto: Bool) -> Bool {
switch (tipo, carrelloVuoto) {
case (.nuovo, false):
return true
default:
return false
}
}
Le tuple sono utili per leggere combinazioni di stati senza creare subito un tipo dedicato. Se la combinazione diventa centrale nel dominio, crea un tipo.
Pattern matching nel motore coupon
enum RegolaCoupon {
case minimoCarrello(cent: Int)
case categoriaRichiesta(CategoriaProdotto)
case primoOrdineSolo
}
func regolaSoddisfatta(_ regola: RegolaCoupon, da contesto: ContestoCoupon) -> Bool {
switch regola {
case .minimoCarrello(let cent):
return contesto.totaleCarrelloInCent >= cent
case .categoriaRichiesta(let categoria):
return contesto.categorieNelCarrello.contains(categoria)
case .primoOrdineSolo:
return contesto.utenteHaGiaOrdinato == false
}
}
Questa funzione legge quasi come una specifica.
Evitare default quando puoi
Se uno switch copre tutti i casi, evita default.
switch sconto {
case .percentuale:
...
case .importoFisso:
...
case .spedizioneGratuita:
...
}
Se aggiungerai un nuovo caso, il compilatore ti avvisera.
Con default, invece, il nuovo caso finirà dentro un ramo generico e potresti non accorgertene.
Checklist
Usa switch quando:
- devi gestire tutti i casi;
- vuoi che il compilatore ti aiuti quando il modello cambia;
- la logica del dominio dipende dallo stato.
Usa if case quando:
- ti interessa un solo caso;
- vuoi estrarre un valore associato in modo leggero.
Usa guard case quando:
- una funzione può proseguire solo in uno stato specifico.
Esercizio
Usando gli enum della lezione precedente, scrivi:
descrivi(_ sconto: Sconto) -> String;valida(_ sconto: Sconto) -> Bool;regolaSoddisfatta(_ regola: RegolaCoupon, da contesto: ContestoCoupon) -> Bool;- una funzione che usa
guard caseper procedere solo se un pagamento è completato.
Evita default dove puoi.