cat swift/struct-class-enum-protocol.md

Lezione 07 40 min

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.