1. Introduzione
Immagina di progettare un'auto moderna. Dentro ci sono centinaia di parti complesse, elettronica delicata, impostazioni fini. Ovviamente, nessuno dovrebbe poter accedere facilmente al blocco di controllo del motore e iniziare a girare qualche vite a caso. Se ognuno mette mano dove non dovrebbe, la macchina potrebbe comportarsi in modo imprevedibile e tu dovresti fare un check-up per capire chi ha rotto cosa.
Lo stesso vale nella programmazione. Quando crei una classe, vuoi proteggere le sue "interiora" dalle mani altrui — così nessuno rovina accidentalmente dati importanti o interferisce con metodi che il mondo esterno non dovrebbe nemmeno conoscere. Ecco il senso dell'incapsulamento — uno dei tre principi chiave della programmazione orientata agli oggetti.
L'incapsulamento ti permette di nascondere i dettagli dell'implementazione e di definire chiaramente cosa è accessibile "da fuori" e cosa deve restare dentro. È come se aprissi il cassetto portaoggetti all'utente, ma tenessi il cofano ben chiuso a chiave. La classe decide da sola quali dati mostrare al codice esterno e quali tenere segreti. Così il codice diventa più affidabile, logico e facile da mantenere.
Per dirla in modo più preciso, incapsulamento è un modo per unire dati (campi) e comportamento (metodi) collegati a questi dati in un'unica struttura, limitando l'accesso diretto ai componenti interni dell'oggetto.
2. Modificatori di accesso: difendiamo il nostro territorio
In C# (e in altri linguaggi OOP) incapsulamento si realizza tramite i modificatori di accesso. Sono delle "etichette" che definiscono quali membri della classe (campi, metodi, proprietà, ecc.) sono accessibili dall'esterno e quali solo dall'interno.
Li abbiamo già incontrati, ma ricordiamo cosa sappiamo e allarghiamo un po' la lista dei modificatori:
| Modificatore | Accessibile da... |
|---|---|
|
A tutti! Qualsiasi codice nel progetto (e anche fuori, se il progetto è una libreria) |
|
Solo dall'interno della stessa classe |
|
Da questa classe e da tutte le sue sottoclassi |
|
Solo all'interno dell'assembly (progetto) corrente |
|
O dall'assembly corrente, o da una sottoclasse |
|
Solo da una sottoclasse all'interno dell'assembly corrente |
In questa lezione ci concentriamo sui più usati: public, private e accenniamo a protected, per non farti fondere il cervello.
Separiamo interno ed esterno
Dai, scriviamo una classe Dog:
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} dice: Bau!");
}
}
Qui tutti i campi e i metodi sono dichiarati public. Questo significa che chiunque può cambiare il nome o l'età del cane:
Dog rex = new Dog("Rex", 5);
rex.Name = "Sharik"; // Cambio di identità inaspettato!
rex.Age = -999; // Problemi seri con l'età
Eppure i campi Name e Age sono dati fondamentali che non vuoi lasciare in mano a chiunque.
Così non si fa
Lasciando tutti i campi public, rischi di rompere la logica della classe: qualsiasi intervento esterno può rendere l'oggetto non valido. Per esempio, dare al cane un'età negativa o un nome tipo "%$#!??".
3. Nascondiamo i campi: usiamo private
Di solito i campi di una classe si fanno privati (con il modificatore private). Questo significa che puoi cambiarne il valore solo dall'interno della classe stessa, e mai da fuori.
public class Dog
{
private string name;
private int age;
public void Bark()
{
Console.WriteLine($"{name} dice: Bau!");
}
}
Ora se provi ad accedere ai campi da fuori:
Dog rex = new Dog("Rex", 5);
rex.name = "Sharik"; // Errore di compilazione
Il compilatore ti dice subito: "Accesso negato!".
Perché così rigido? E la flessibilità?
Tutta la "flessibilità" si ottiene tramite metodi speciali o proprietà (ne parleremo meglio nella prossima lezione), che permettono di controllare l'accesso e modificare i dati solo secondo certe regole.
4. Incapsulamento in pratica: esempio con controllo
Immagina che vogliamo impedire che un cane abbia un'età negativa:
public class Dog
{
private string name;
private int age;
public Dog(string name, int age)
{
this.name = name;
if (age >= 0)
this.age = age;
else
this.age = 0; // Non permettiamo un'età "strana"
}
public void Bark()
{
Console.WriteLine($"{name} dice: Bau!");
}
// Metodo per cambiare l'età in modo sicuro
public void SetAge(int newAge)
{
if (newAge >= 0)
age = newAge;
// Puoi aggiungere un else: messaggio di valore non valido
}
}
Ora nessuno da fuori può rovinare i campi direttamente. Per cambiare l'età c'è un metodo apposito che fa il controllo necessario.
5. Campi contro metodi di accesso (getter/setter)
Questo modo — rendere i campi private e fornire metodi per lavorarci — si chiama incapsulamento dei dati (data encapsulation). Per leggere il valore di un campo spesso si crea un metodo getter, per scrivere — un setter.
public class Dog
{
private string name;
public Dog(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
public void SetName(string newName)
{
// Qui puoi aggiungere un controllo sul nome
name = newName;
}
}
Ma! Con l'arrivo delle proprietà (properties) in C#, questi metodi si usano sempre meno — le proprietà rendono il codice molto più pulito e comodo (ne parleremo nella prossima lezione).
6. Modificatori di accesso per metodi e classi
I campi non sono tutto! I modificatori di accesso si usano anche per i metodi (funzioni membro della classe) e persino per le classi stesse.
I metodi che servono solo all'interno della classe (tipo funzioni di supporto per la logica interna), di solito si fanno private.
I metodi pubblici sono il cosiddetto "interfaccia" della classe (da non confondere con la parola chiave interface), cioè quello che l'utente della classe può usare.
7. Errori tipici con l'incapsulamento
Errore n°1: tutto viene dichiarato public.
Sembra che più cose sono accessibili, più è facile usare la classe. In realtà così apri l'accesso a parti interne che non dovrebbero essere cambiate da fuori. Questo rende il codice vulnerabile e imprevedibile, soprattutto nei progetti grandi.
Errore n°2: cambiare il modificatore di accesso rompe il codice esterno.
Durante il refactoring puoi cambiare per sbaglio il modificatore di accesso di un metodo o campo, e tutto il codice che lo usava smette di compilare o si comporta male. Questo è critico soprattutto nelle API pubbliche.
Errore n°3: confusione tra variabili locali e campi della classe.
A volte gli sviluppatori dimenticano che una variabile dichiarata dentro un metodo vive solo lì. I campi della classe invece sono accessibili in tutti i suoi metodi. Questo porta a errori poco chiari, soprattutto se i nomi delle variabili coincidono.
Errore n°4: ignorare private e protected.
Molti hanno paura di usare accesso limitato, temendo di non poter più accedere a ciò che serve. Ma l'incapsulamento serve proprio a questo — nascondere tutto il superfluo e mostrare solo ciò che davvero serve all'esterno.
GO TO FULL VERSION