cat swift/access-control-confini-modulo.md
Access control è confini del modulo
public, internal, private e private(set): proteggere invarianti e rendere chiaro cosa fa parte dell'API.
Modellare bene il dominio non significa solo creare tipi giusti. Significa anche proteggere i loro confini.
Swift offre l’access control per decidere chi può leggere, costruire o modificare un tipo.
I livelli più comuni sono:
private: visibile solo nello scope corrente;fileprivate: visibile nello stesso file;internal: visibile nel modulo, default;public: visibile fuori dal modulo;open: comepublic, ma pensato per ereditarietà e override fuori modulo.
In questo corso useremo soprattutto private, internal, public e private(set).
Perché i confini contano
Nel laboratorio precedente avevamo:
struct Prezzo {
let centesimi: Int
init?(_ centesimi: Int) {
guard centesimi >= 0 else {
return nil
}
self.centesimi = centesimi
}
}
Questo protegge da prezzi negativi, ma centesimi è leggibile da tutti. Va bene. Il problema nasce quando una proprietà è modificabile.
struct Coupon {
var codice: String
var scontoPercentuale: Int
}
Chiunque può scrivere:
coupon.scontoPercentuale = 999
Il tipo non protegge il suo dominio.
private(set)
private(set) permette di leggere una proprietà dall’esterno, ma modificarla solo dall’interno del tipo.
struct Carrello {
private(set) var righe: [RigaCarrello] = []
mutating func aggiungi(_ riga: RigaCarrello) {
righe.append(riga)
}
}
Chi usa Carrello può leggere righe, ma non può sostituirle liberamente.
Questo consente al tipo di controllare le modifiche.
Initializer come porta d’ingresso
Gli initializer sono confini fondamentali.
struct CodiceCoupon: Equatable {
let valore: String
init?(_ valore: String) {
let normalizzato = valore.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
guard normalizzato.isEmpty == false else {
return nil
}
self.valore = normalizzato
}
}
Ora un codice vuoto non può esistere come CodiceCoupon.
Ma se valore è pubblico e modificabile, perdi tutto:
struct CodiceCoupon {
var valore: String
}
Il tipo deve proteggere il suo stato anche dopo la creazione.
public API e dettagli interni
Quando creeremo un package Swift, dovremo distinguere:
- cosa chi usa la libreria può vedere;
- cosa resta dettaglio interno.
Esempio:
public struct CouponEngine {
public init() {}
public func applica(_ coupon: Coupon, a carrello: Carrello) throws -> Carrello {
...
}
}
Se CouponEngine deve essere usato fuori dal modulo, deve essere public.
Ma una funzione di supporto può restare private:
private func calcolaRiduzione(...) -> Int {
...
}
Non tutto merita di diventare API.
internal come default
In Swift, se non scrivi nulla, il livello è internal: visibile dentro lo stesso modulo.
Per una app piccola va bene. Per una libreria, invece, devi essere intenzionale: tutto ciò che è public diventa contratto.
Cambiare un dettaglio interno è facile. Cambiare un’API pubblica rompe chi la usa.
Nascondere invarianti
Supponiamo di voler evitare il force unwrap in Prezzo.
Possiamo creare un initializer privato sicuro da usare internamente:
struct Prezzo: Equatable {
let centesimi: Int
init?(_ centesimi: Int) {
guard centesimi >= 0 else {
return nil
}
self.centesimi = centesimi
}
private init(validato centesimi: Int) {
self.centesimi = centesimi
}
func sommando(_ altro: Prezzo) -> Prezzo {
Prezzo(validato: centesimi + altro.centesimi)
}
}
Fuori dal tipo, chi crea un prezzo deve passare dalla validazione. Dentro il tipo, dove conosciamo l’invariante, possiamo costruire direttamente.
Estendere senza esporre troppo
Un errore comune è rendere pubbliche proprietà solo per usarle nei test o in altri file. Prima chiediti se stai rompendo l’incapsulamento.
Alternative:
- aggiungere metodi espliciti;
- usare
private(set); - spostare logica nello stesso tipo;
- creare inizializzatori di test in target separati;
- verificare comportamenti invece di dettagli.
Checklist
Quando progetti un tipo, chiediti:
- quali proprietà devono essere lette dall’esterno?
- quali possono essere modificate?
- chi deve poter costruire valori validi?
- cosa è dettaglio interno?
- cosa diventerà API pubblica?
- sto esponendo stato per comodità o per necessità?
Esercizio
Rivedi i tipi del motore coupon:
CodiceCoupon;Prezzo;ScontoPercentuale;Carrello;CouponEngine.
Per ciascuno decidi:
- quali proprietà sono
let; - quali sono
private(set); - quali initializer sono pubblici;
- quali helper restano
private.
Poi scrivi un esempio di stato invalido che l’access control ora impedisce.