cat swift/composizione-vs-ereditarieta.md
Composizione vs ereditarietà
In Swift spesso è meglio comporre piccoli tipi e comportamenti invece di costruire gerarchie rigide.
L’ereditarietà è uno strumento potente, ma in Swift non è il punto di partenza. Molto codice idiomatico preferisce la composizione: costruire tipi più grandi combinando tipi piccoli.
La composizione rende le dipendenze più visibili, riduce accoppiamento e funziona molto bene con struct, enum e protocol.
Il problema delle gerarchie
Immagina di modellare promozioni:
class Promozione {
func sconto(per carrello: Carrello) -> Prezzo {
.zero
}
}
class PromozionePercentuale: Promozione {
override func sconto(per carrello: Carrello) -> Prezzo {
...
}
}
class PromozioneCategoria: Promozione {
override func sconto(per carrello: Carrello) -> Prezzo {
...
}
}
Funziona, ma porta con se alcuni problemi:
- identità di riferimento anche se non serve;
- stato potenzialmente condiviso;
- override difficili da seguire;
- gerarchie rigide quando le regole si combinano;
- test più macchinosi.
Composizione con valori
Un’alternativa:
struct Promozione {
let nome: String
let regole: [RegolaCoupon]
let sconto: Sconto
}
Ora una promozione non è una sottoclasse. È una combinazione di:
- nome;
- regole;
- sconto.
Puoi creare molte promozioni senza creare nuove classi.
Comporre comportamenti
Con una struct generica:
struct Regola<Input> {
let descrizione: String
let valida: (Input) -> Bool
}
Puoi comporre regole:
let carrelloMinimo = Regola<ContestoCoupon>(
descrizione: "Carrello minimo 50 euro",
valida: { $0.carrello.totaleInCent >= 5000 }
)
let primoOrdine = Regola<ContestoCoupon>(
descrizione: "Solo primo ordine",
valida: { $0.utente.haGiaOrdinato == false }
)
Il comportamento è dato dalla composizione di valori, non dall’ereditarietà.
Composizione con protocol
Puoi comporre anche dipendenze:
struct CouponService {
let repository: CouponRepository
let engine: CouponEngine
}
CouponService non eredita da niente. Riceve ciò che gli serve.
Questo rende chiaro il suo mondo:
- da dove arrivano i coupon;
- chi li valida;
- chi applica lo sconto.
Quando l’ereditarietà ha senso
L’ereditarietà non è vietata.
Ha senso quando:
- un framework la richiede;
- stai lavorando con classi UIKit/AppKit;
- devi specializzare comportamento in una gerarchia già esistente;
- hai un vero rapporto “è un tipo di” e non solo “usa”.
Ma nel dominio puro, spesso puoi evitarla.
Rapporto is-a vs has-a
Ereditarietà:
class CouponPercentuale: Coupon {}
Sta dicendo: un coupon percentuale è un coupon.
Composizione:
struct Coupon {
let codice: CodiceCoupon
let sconto: Sconto
}
Sta dicendo: un coupon ha uno sconto.
Spesso il secondo modello è più flessibile.
Composizione e test
Un tipo composto è più facile da testare.
let service = CouponService(
repository: RepositoryInMemoria(coupon: [...]),
engine: CouponEngine()
)
Non devi creare sottoclassi finte. Puoi passare implementazioni piccole e controllate.
Odori di ereditarietà
Fai attenzione quando:
- una sottoclasse disabilita metodi della superclasse;
- devi controllare il tipo concreto con
isoas?; - la gerarchia cresce in profondità;
- la superclasse contiene stato usato solo da alcune sottoclassi;
- i test richiedono molta preparazione.
Questi segnali indicano che forse la composizione sarebbe più chiara.
Checklist
Preferisci composizione quando:
- stai modellando dati di dominio;
- vuoi combinare regole;
- vuoi testare facilmente;
- il comportamento cambia per configurazione;
- puoi usare enum o struct invece di sottoclassi.
Valuta ereditarietà quando:
- il framework lo richiede;
- devi estendere una classe esistente;
- il rapporto gerarchico è davvero stabile.
Esercizio
Parti da questa idea:
class Promozione {}
class PromozionePercentuale: Promozione {}
class PromozioneCategoria: Promozione {}
class PromozionePrimoOrdine: Promozione {}
Riscrivila usando:
struct Promozione;enum Sconto;[RegolaCoupon];CouponEngine.
Poi aggiungi una nuova promozione combinata: “10% sui libri solo al primo ordine con carrello minimo 50 euro”. Dovresti riuscirci senza creare nuove classi.