cat swift/extensions-comportamenti.md

Lezione 13 35 min

Extensions per organizzare comportamenti

Le extensions permettono di separare responsabilità, conformità e helper senza frammentare il modello del dominio.

Le extension sono uno strumento centrale nello stile Swift. Permettono di aggiungere proprietà calcolate, metodi, initializer e conformità a tipi esistenti.

Non servono a “spargere codice”. Servono a organizzare comportamenti collegati allo stesso tipo.

Aggiungere comportamento a un tipo

struct Prezzo {
  let centesimi: Int
}

extension Prezzo {
  var euro: Double {
    Double(centesimi) / 100
  }
}

Ora Prezzo ha una rappresentazione in euro, ma il dato interno resta in centesimi.

let prezzo = Prezzo(centesimi: 1299)
print(prezzo.euro)

Separare conformità

Una pratica comune e dichiarare la conformità in extension.

struct Coupon {
  let codice: String
  let sconto: Sconto
}

extension Coupon: Equatable {}

Per conformità semplici, questo mantiene chiara la separazione: prima il modello, poi i contratti supportati.

Extension con metodi di dominio

extension Carrello {
  var totaleInCent: Int {
    righe.reduce(0) { totale, riga in
      totale + riga.subtotaleInCent
    }
  }

  func contieneCategoria(_ categoria: CategoriaProdotto) -> Bool {
    righe.contains { riga in
      riga.prodotto.categoria == categoria
    }
  }
}

Questi metodi appartengono al dominio del carrello. Non sono utility generiche.

Evitare helper globali

Questo è meno espressivo:

func totaleCarrello(_ carrello: Carrello) -> Int {
  ...
}

Spesso è meglio:

carrello.totaleInCent

Il comportamento vive vicino al dato a cui appartiene.

Organizzare per responsabilità

Puoi usare più extension per raggruppare responsabilità.

extension Coupon {
  var descrizione: String {
    ...
  }
}

extension Coupon: Codable {}

extension Coupon: Identifiable {
  var id: String { codice.valore }
}

Questo è utile quando un tipo cresce.

Attenzione pero: se hai troppe extension enormi, forse il tipo ha troppe responsabilità.

Extension sui tipi standard

Puoi estendere anche tipi standard:

extension Array where Element == RigaCarrello {
  var totaleInCent: Int {
    reduce(0) { totale, riga in
      totale + riga.subtotaleInCent
    }
  }
}

Questa tecnica è potente, ma va usata con cautela. Estendere Array globalmente può rendere il codice meno ovvio se il comportamento è troppo specifico.

Spesso è meglio mantenere il comportamento dentro un tipo di dominio:

struct Carrello {
  let righe: [RigaCarrello]
}

Extension e access control

Le extension rispettano l’access control.

extension CouponEngine {
  private func calcolaRiduzione(...) -> Int {
    ...
  }
}

Puoi usarle per tenere separati gli helper privati dalla superficie pubblica.

Initializer in extension

Puoi aggiungere initializer secondari:

extension ContestoCoupon {
  init(carrello: Carrello, utente: Utente) {
    self.init(
      totaleCarrelloInCent: carrello.totaleInCent,
      categorieNelCarrello: carrello.categorie,
      utenteHaGiaOrdinato: utente.haGiaOrdinato
    )
  }
}

Questo rende chiara la trasformazione da modello operativo a contesto di validazione.

Quando non usare extension

Non usare extension per nascondere il fatto che il tipo è troppo grande.

Se hai:

  • 10 extension;
  • molte responsabilità scollegate;
  • nomi generici come Helpers;
  • metodi che manipolano dettagli lontani;

forse devi dividere il modello.

Checklist

Usa extension per:

  • aggiungere comportamenti coerenti con un tipo;
  • separare conformità;
  • raggruppare responsabilità;
  • aggiungere initializer secondari;
  • tenere helper privati separati dalla API principale.

Evita extension quando:

  • stai creando un cestino di metodi;
  • il comportamento non appartiene davvero al tipo;
  • stai estendendo tipi standard con logica troppo specifica;
  • stai mascherando un tipo troppo grande.

Esercizio

Riorganizza il motore coupon:

  • modello base dei tipi nella dichiarazione principale;
  • conformità Equatable in extension;
  • metodi di calcolo del carrello in extension;
  • initializer di ContestoCoupon da Carrello e Utente;
  • helper privati di CouponEngine in extension privata.

Poi leggi il file dall’alto verso il basso e chiediti: il dominio emerge chiaramente?