L'incapsulamento è lo strumento con cui si realizza l'information hiding.
class ContoCorrente
{
public string Intestatario;
public decimal Saldo; // chiunque può modificarlo!
}
ContoCorrente c = new ContoCorrente();
c.Intestatario = "Mario Rossi";
c.Saldo = 1000m;
c.Saldo = -50000m; // stato incoerente: nessun controllo| Modificatore | Stessa classe | Classe derivata | Esterno |
|---|---|---|---|
public |
sì | sì | sì |
protected |
sì | sì | no |
private |
sì | no | no |
In C# il default per i membri di una classe è private.
privateprivate e si allarga solo se
necessarioclass ContoCorrente
{
private string _intestatario; // nascosto
private decimal _saldo; // nascosto
public ContoCorrente(string intestatario, decimal saldoIniziale)
{
_intestatario = intestatario;
_saldo = saldoIniziale >= 0 ? saldoIniziale : 0;
}
public void Versa(decimal importo)
{
if (importo > 0) _saldo += importo;
}
public bool Preleva(decimal importo)
{
if (importo <= 0 || importo > _saldo) return false;
_saldo -= importo;
return true;
}
public decimal LeggiSaldo() { return _saldo; }
}_saldo, _intestatarioSaldo, VersaIn altri linguaggi (Java) si scrivono coppie di metodi:
public string GetIntestatario() { return _intestatario; }
public void SetIntestatario(string v) { _intestatario = v; }C# offre una sintassi dedicata: le proprietà.
Si usano come campi (c.Intestatario = "...") ma sotto sono
metodi.
class ContoCorrente
{
private decimal _saldo;
public decimal Saldo
{
get { return _saldo; } // accessor di lettura
private set { _saldo = value; } // accessor di scrittura
}
}get: codice eseguito
quando si legge la proprietàset: codice eseguito
quando si assegna alla proprietà; value contiene il nuovo valoreset è privato)class Persona
{
private int _eta;
public int Eta
{
get { return _eta; }
set
{
if (value < 0 || value > 130)
throw new ArgumentOutOfRangeException(nameof(value));
_eta = value;
}
}
}
Persona p = new Persona();
p.Eta = 25; // OK
p.Eta = -3; // eccezione: stato non validoLa proprietà fa da guardiano del campo.
class ContoCorrente
{
private decimal _saldo;
// Solo get: dall'esterno è leggibile ma non scrivibile
public decimal Saldo { get { return _saldo; } }
public void Versa(decimal importo)
{
if (importo > 0) _saldo += importo;
}
}
ContoCorrente c = new ContoCorrente();
decimal x = c.Saldo; // OK: lettura
// c.Saldo = 1000m; // ERRORE: nessun setter pubblicoQuando il get/set non contiene logica, si può usare la sintassi sintetica:
class Persona
{
// Il compilatore genera automaticamente un campo nascosto
public string Nome { get; set; }
public int Eta { get; set; }
// Sola lettura dall'esterno: si imposta solo dal costruttore
public string Codice { get; }
public Persona(string nome, int eta, string codice)
{
Nome = nome;
Eta = eta;
Codice = codice;
}
}class Rettangolo
{
public double Base { get; private set; }
public double Altezza { get; private set; }
// Proprietà calcolata: nessun campo, valore derivato dagli altri
public double Area => Base * Altezza;
public Rettangolo(double b, double h)
{
Base = b;
Altezza = h;
}
public void Scala(double fattore)
{
Base *= fattore;
Altezza *= fattore;
}
}L'esterno vede una temperatura: come è memorizzata internamente non importa.
class Temperatura
{
private double _kelvin; // scelta interna: gradi Kelvin
public double Celsius
{
get { return _kelvin - 273.15; }
set { _kelvin = value + 273.15; }
}
public double Fahrenheit
{
get { return _kelvin * 9.0 / 5.0 - 459.67; }
set { _kelvin = (value + 459.67) * 5.0 / 9.0; }
}
}Domani si può cambiare _kelvin in _celsius senza che il codice cliente se ne
accorga.
Non tutti i metodi devono essere pubblici: quelli ausiliari restano
private.
class CodiceFiscale
{
private string _codice;
public CodiceFiscale(string codice)
{
if (!Valida(codice))
throw new ArgumentException("Codice fiscale non valido");
_codice = codice.ToUpper();
}
public string Codice { get { return _codice; } }
// Dettaglio implementativo: non interessa a chi usa la classe
private static bool Valida(string c)
{
return c != null && c.Length == 16;
}
}Un invariante è una proprietà che deve essere sempre vera per gli oggetti di una classe.
class Frazione
{
// Invariante: denominatore != 0, frazione sempre semplificata
private int _num;
private int _den;
public int Numeratore { get { return _num; } }
public int Denominatore { get { return _den; } }
public Frazione(int num, int den)
{
if (den == 0) throw new ArgumentException("Denominatore zero");
int g = MCD(Math.Abs(num), Math.Abs(den));
_num = num / g;
_den = den / g;
}
private static int MCD(int a, int b) => b == 0 ? a : MCD(b, a % b);
}L'incapsulamento garantisce che chi usa Frazione non possa mai romperne lo stato.
class Stack
{
private int[] _dati;
private int _cima; // indice del prossimo slot libero
public Stack(int capacita)
{
_dati = new int[capacita];
_cima = 0;
}
public int Conteggio => _cima;
public bool Vuoto => _cima == 0;
public bool Pieno => _cima == _dati.Length;
public void Push(int v)
{
if (Pieno) throw new InvalidOperationException("Stack pieno");
_dati[_cima++] = v;
}
public int Pop()
{
if (Vuoto) throw new InvalidOperationException("Stack vuoto");
return _dati[--_cima];
}
}_dati, l'indice _cima, la capacità internaPush, Pop, Conteggio, Vuoto,
Pieno_dati[_cima] = ...List<int> senza modificare nulla
all'esternoStack s = new Stack(10);
s.Push(1);
s.Push(2);
s.Push(3);
Console.WriteLine(s.Conteggio); // 3
Console.WriteLine(s.Pop()); // 3
Console.WriteLine(s.Pop()); // 2
Console.WriteLine(s.Vuoto); // false
// s._dati[0] = 99; // ERRORE: campo privato
// s._cima = -1; // ERRORE: campo privatoL'oggetto è una scatola nera: si interagisce solo con l'interfaccia pubblica.
private (o al
massimo protected se serve a una
gerarchia)public è un impegno verso l'esterno| Concetto | Sintassi C# |
|---|---|
| Campo privato | private tipo _campo; |
| Proprietà completa | public T P { get { ... } set { ... } } |
| Proprietà automatica | public T P { get; set; } |
| Sola lettura | public T P { get; } |
| Set privato | public T P { get; private set; } |
| Concetto | Sintassi C# |
|---|---|
| Proprietà calcolata | public T P => espressione; |
| Validazione nel setter | controllo su value prima di
assegnare |
| Metodo helper interno | private invece di public |
| Convenzione campo privato | nome con underscore: _saldo |
"Programs must be written for people to read, and only incidentally for machines to execute." — Harold Abelson