cat swift/struct-class-enum-protocol.md
Scegliere tra struct, class, enum e protocol
La modellazione del dominio comincia scegliendo il tipo giusto: valori, identità, stati chiusi e contratti.
Nel primo modulo abbiamo iniziato a usare struct, enum e optionals per dare forma a un piccolo carrello. Ora facciamo un passo più importante: capire come scegliere lo strumento giusto quando il dominio cresce.
Swift offre quattro strumenti fondamentali per modellare:
struct: valori;class: identità condivisa;enum: stati e alternative chiuse;protocol: contratti e capacità.
Il problema non è sapere la sintassi di ciascuno. Il problema è capire quale domanda risolve.
Struct: quando conta il valore
Una struct è la scelta naturale per rappresentare dati di dominio.
struct Prodotto {
let id: String
let nome: String
let prezzoInCent: Int
}
Un prodotto, una riga d’ordine, un indirizzo, una configurazione, un filtro di ricerca: tutti questi sono valori. Li puoi confrontare, copiare, passare a funzioni, salvare e ricostruire.
La regola pratica è:
Se due istanze con gli stessi dati sono equivalenti per il dominio, probabilmente vuoi una struct.
Class: quando conta l’identità
Una class serve quando l’identità dell’oggetto è più importante del suo contenuto, oppure quando devi condividere stato vivo tra più parti.
final class SessioneCheckout {
private(set) var stepCorrente: Int = 0
func avanza() {
stepCorrente += 1
}
}
Qui non stiamo descrivendo un semplice dato: stiamo rappresentando una sessione viva. Se due variabili puntano alla stessa sessione, modificare una modifica anche l’altra.
Usa class con intenzione. In Swift non è il default. È una scelta più pesante, perché introduce condivisione, ARC, possibili reference cycles e ragionamento più complesso.
Enum: quando vuoi chiudere il mondo
Un enum rappresenta un insieme finito di possibilità.
enum StatoOrdine {
case creato
case pagato
case spedito
case consegnato
case annullato
}
Questo è molto più forte di una stringa:
let stato = "spedito"
Con una stringa puoi scrivere "Spedito", "spedto", "shipping", "". Con un enum puoi scegliere solo stati validi.
Gli enum sono uno dei superpoteri di Swift perché ti permettono di rendere impossibili molti stati sbagliati.
Protocol: quando vuoi descrivere una capacità
Un protocol definisce un contratto:
protocol Scontabile {
var scontoInCent: Int { get }
}
Un protocollo non dovrebbe essere creato solo per “astrarre”. Dovrebbe nascere quando nel dominio esiste davvero una capacità comune.
struct Coupon: Scontabile {
let scontoInCent: Int
}
struct PromozioneCategoria: Scontabile {
let categoria: CategoriaProdotto
let scontoInCent: Int
}
Qui Scontabile ha senso solo se il sistema tratta più tipi come fonti di sconto.
Un errore tipico: classi ovunque
Chi arriva da lìnguaggi molto orientati agli oggetti spesso parte così:
class Product {
var name: String
var price: Int
init(name: String, price: Int) {
self.name = name
self.price = price
}
}
Questo codice funziona, ma introduce mutabilità e identità condivisa anche dove non servono.
In Swift, una versione più naturale è:
struct Prodotto {
let nome: String
let prezzoInCent: Int
}
Meno superficie mutabile, più chiarezza.
Esempio: dominio coupon
Partiamo da un modello ingenuo:
struct Coupon {
let codice: String
let tipo: String
let valore: Int
}
Qui tipo è fragile. Che valori sono ammessi? "percentuale"? "fixed"? "euro"? Inoltre valore cambia significato a seconda del tipo.
Meglio:
enum TipoSconto {
case percentuale(Int)
case importoFisso(Int)
}
struct Coupon {
let codice: String
let sconto: TipoSconto
}
Ora non abbiamo più due campi scollegati. Lo sconto è un concetto unico.
Protocol senza dogmi
Non creare un protocollo se hai un solo tipo e nessuna reale variabilità.
Questo è spesso inutile:
protocol ProdottoProtocol {
var nome: String { get }
var prezzoInCent: Int { get }
}
struct Prodotto: ProdottoProtocol {
let nome: String
let prezzoInCent: Int
}
Il protocollo non aggiunge significato. Aggiunge solo un livello.
Un protocollo diventa utile quando hai più implementazioni intercambiabili o vuoi separare un contratto da una dipendenza concreta.
Checklist decisionale
Quando devi modellare un concetto, chiediti:
- È un valore? Parti da
struct. - È uno stato tra alternative finite? Usa
enum. - Ha identità condivisa o ciclo di vita? Valuta
class. - Descrive una capacità comune tra più tipi? Valuta
protocol. - Sto aggiungendo astrazione per un bisogno reale o per abitudine?
Esercizio
Nel dominio coupon, modella:
- un
Prodotto; - una
CategoriaProdotto; - un
Coupon; - uno
Sconto; - una
SessioneCheckout.
Per ogni tipo scegli tra struct, class, enum e protocol. Poi scrivi una riga di motivazione.
L’obiettivo non è finire in fretta: è allenare il giudizio.