CodeGym /Corsi /C# SELF /Errori con i modificatori di accesso e l'incapsulamento

Errori con i modificatori di accesso e l'incapsulamento

C# SELF
Livello 25 , Lezione 2
Disponibile

1. Introduzione

I modificatori di accesso sono come recinzioni e serrature a casa tua: decidono chi può entrare in una stanza e chi può solo sbirciare dal buco della serratura. Ricordati, in C# sono questi:

Modificatore Chi vede
public
Tutti
private
Solo dentro la classe attuale
protected
Classe attuale e i figli (ereditarietà)
internal
Tutto il progetto (assembly)
protected internal
Tutto il progetto + i figli
private protected
Solo i figli nello stesso progetto

Il compito dell'incapsulamento è nascondere i dettagli dell'implementazione della classe, così nessuno può rovinare il tuo oggetto con una brutta riga tipo obj.Field = 99999;, se non vuoi che succeda.

2. Errori classici con i livelli di accesso

Aprire tutto con public

L'errore più comune è dichiarare tutti i campi e i metodi della classe come public. Il ragionamento è tipo: "Magari serve? Così gli altri possono usare i miei membri dove vogliono!". Sembra comodo, finché qualcuno non mette il tuo oggetto in uno stato assurdo, o inizia a usare i tuoi dettagli interni e poi cambiare il codice fa paura.

Esempio di errore:


public class BankAccount
{
    public string Owner;
    public decimal Balance;
}

Ora chiunque può fare così:


var acc = new BankAccount();
acc.Owner = "";
acc.Balance = -1000000M; // Improvvisamente, un debito da un milione!

Cosa c'è che non va?
Hai rotto tutte le regole di sicurezza e buon senso. Anche se la tua banca contiene solo foglie o soldi finti di "Monopoli", un saldo negativo "a piacere" è strano.

Trasformare la classe in una "structure senza struttura"

Il secondo errore tipico è rendere pubblici non solo i campi, ma anche proprietà e metodi interni che non dovrebbero essere accessibili da fuori.


public class Vault
{
    public string DoorPIN = "1234";
    public void OpenDoor() 
    {
        // Un po' di magia
    }
}

E adesso? Succede che chiunque può sapere il PIN e aprire la porta del tuo caveau. Anche se è un "caveau di test", tra un mese qualcuno si dimentica di questo buco e succede un casino.

Approccio giusto:
L'interfaccia della classe deve essere minima e protetta. Quello che serve agli altri — public. Quello che serve solo a te — private. Se serve ai figli — protected.

3. Errori di incapsulamento

In .NET c'è una regola chiara: nessun dato della classe (stato) deve stare in campi pubblici! Tutto deve essere chiuso o almeno incapsulato in proprietà. Accesso diretto ai campi = stile pessimo e fonte di bug difficili da trovare.

Perché i campi devono essere private

I campi sono la base dello stato interno dell'oggetto. Se li lasci pubblici, perdi il controllo su quando e che valori ci finiscono. Il tuo oggetto diventa vulnerabile a valori sbagliati, e se cambi il formato, tutte le chiamate pubbliche si rompono.

Meglio così:
Dichiara i campi come private e mostra fuori solo le proprietà o i metodi necessari:


public class BankAccount
{
    private decimal balance;

    public decimal Balance
    {
        get { return balance; }
        private set 
        {
            if (value < 0) 
                throw new ArgumentException("Il saldo non può essere negativo");
            balance = value;
        }
    }

    public BankAccount(decimal initialBalance)
    {
        Balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("Non puoi depositare zero o meno!");
        Balance += amount;
    }
}

Errore grave: public set per dati che non vanno cambiati

A volte serve solo il get, ma i programmatori scrivono public { get; set; } "di default", perché è più veloce. Così chiunque può fare:


account.Balance = 99999999; // Perché no?

Meglio così:
Se la proprietà va settata solo dentro la classe, metti il set con modificatore:


public decimal Balance { get; private set; }

oppure, se il valore va dato solo alla creazione dell'oggetto (C# 14):


public decimal Balance { get; init; }

4. Quando anche protected è una trappola

Porte spalancate per tutti i figli

protected è un buon modo per passare funzionalità ai figli, ma certi dati è meglio non renderli accessibili nemmeno agli eredi! Per esempio, se i tuoi figli (classi derivate) non devono toccare campi critici direttamente.

Esempio:


public class SecureVault
{
    protected string secretCode = "1234";
}

Ora ogni figlio può fare così:


public class HackerVault : SecureVault
{
    public void Hack()
    {
        secretCode = "0000"; // Cambiato il segreto in un attimo!
    }
}

Consiglio:
Usa protected solo per quello che davvero serve ai figli. Tutto il resto lascialo privato, e per le operazioni necessarie scrivi metodi protetti.

5. Errori con internal e assembly confusi

Il modificatore internal sembra comodo: "così tutto è accessibile nel progetto". Ma appena il progetto cresce o usi librerie, arrivano i casini. Spesso gli studenti rendono qualcosa pubblico nell'assembly, anche se la classe doveva restare nascosta.

Esempio:


internal class Logger
{
    internal void Write(string msg) { /* ... */ }
}

Ora chiunque può chiamare Logger.Write da qualsiasi file del progetto. E se tra un anno qualcuno collega la tua DLL a un altro progetto? Tutta la "cucina interna" sarà visibile, se hai lasciato qualcosa public.

Consiglio:
Usa internal per le classi tecniche, ma lascia i dettagli sensibili comunque private.

6. Pratica: app bancaria

Continuiamo il nostro esempio con l'app bancaria, così vedi nella pratica quanto è facile sbagliare e come rimediare.

Esempio di errore n°1: Campi public


public class BankAccount
{
    public decimal Balance;
    public string Owner;
}

Problema: chiunque può romperlo.

Correggiamo con le proprietà


public class BankAccount
{
    private decimal _balance;
    private string _owner;

    public string Owner => _owner; // Sola lettura

    public decimal Balance 
    { 
        get => _balance; 
        private set 
        {
            if (value < 0) throw new ArgumentException("Il saldo non può essere negativo");
            _balance = value;
        }
    }

    public BankAccount(string owner, decimal initialBalance)
    {
        if (string.IsNullOrWhiteSpace(owner))
            throw new ArgumentException("Il nome del proprietario è obbligatorio");

        _owner = owner;
        Balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("L'importo del deposito deve essere positivo");
        Balance += amount;
    }
}

Ora se provi così:


var acc = new BankAccount("Lena", 1000m);
acc.Balance = -700m; // Errore! set è privato.

O anche così:


acc.Owner = "Hacker"; // Errore di compilazione, niente set

— non funziona. Solo tramite costruttore o metodi.

Errore: Proprietà "per bellezza"

Molti scrivono:


public int Age { get; set; }

Anche se l'età dovrebbe cambiare solo con il metodo IncreaseAge (tipo il 1 gennaio), e non direttamente.

7. Promemoria visivo: come non si fa e come si fa

graph TD
    A[Campi Public] -->|Qualsiasi codice| D[Stato fuori controllo]
    B[Campi Private + Metodi Public] -->|Ben definito| E[Stato controllato]
    F[Setters Public] -->|Chiunque può cambiare| D
    G[Setters Private] -->|Solo la classe| E
Approccio Stato protetto? Si può validare? Facile da estendere?
Campi public
Proprietà (get/set public) Parzialmente Sì, ma non sicuro
Campi private + proprietà con set private

8. Consigli di stile per l'incapsulamento

  • Esporre solo quello che davvero serve all'utente della classe.
  • Valida sempre i dati che entrano nell'oggetto.
  • Non esagerare: se una proprietà non va cambiata da fuori — metti il set privato.
  • Per logica particolare — usa sempre i metodi, non l'accesso diretto.
  • Anche se ti tenta — non fare campi public "per test".
  • Usa le proprietà invece dei campi anche per la sola lettura pubblica — così puoi modificarle facilmente dopo.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION