cat swift/extensions-comportamenti.md
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à
Equatablein extension; - metodi di calcolo del carrello in extension;
- initializer di
ContestoCoupondaCarrelloeUtente; - helper privati di
CouponEnginein extension privata.
Poi leggi il file dall’alto verso il basso e chiediti: il dominio emerge chiaramente?