JavaScript OOP: incapsulamento con campi privati e metodi di accesso

by Anastasia P.
SHARE
JavaScript OOP: incapsulamento con campi privati e metodi di accesso
© Guida-HTML5.it

Introduzione

Quando si parla di programmazione orientata agli oggetti in JavaScript, spesso l’attenzione si concentra su classi, ereditarietà e prototype chain. Un aspetto altrettanto utile, però, è l’incapsulamento: la capacità di nascondere i dettagli interni di un oggetto e di esporre solo ciò che serve davvero all’esterno.

In JavaScript moderno, l’incapsulamento si realizza in modo molto pratico con i campi privati nelle classi, insieme a getter e setter. Questo approccio è particolarmente utile quando vuoi proteggere lo stato interno di un oggetto, prevenire modifiche accidentali e mantenere il codice più robusto e leggibile.

In questo tutorial vedremo come creare una classe con dati privati, come controllarne l’accesso con metodi dedicati e perché questa tecnica è spesso preferibile rispetto all’esposizione diretta delle proprietà.

Codice completo

// Esempio: un conto bancario con saldo privato
class ContoBancario {
  // Campo privato: accessibile solo dall´interno della classe
  #saldo;

  constructor(titolare, saldoIniziale = 0) {
    this.titolare = titolare;

    if (saldoIniziale < 0) {
      throw new Error("Il saldo iniziale non può essere negativo");
    }

    this.#saldo = saldoIniziale;
  }

  // Getter: espone il saldo in modo controllato
  get saldo() {
    return this.#saldo;
  }

  // Setter: valida il nuovo valore prima di assegnarlo
  set saldo(nuovoSaldo) {
    if (typeof nuovoSaldo !== "number" || Number.isNaN(nuovoSaldo)) {
      throw new Error("Il saldo deve essere un numero valido");
    }

    if (nuovoSaldo < 0) {
      throw new Error("Il saldo non può essere negativo");
    }

    this.#saldo = nuovoSaldo;
  }

  // Metodo pubblico per depositare denaro
  deposita(importo) {
    if (typeof importo !== "number" || importo <= 0) {
      throw new Error("L´importo da depositare deve essere maggiore di zero");
    }

    this.#saldo += importo;
    return this.#saldo;
  }

  // Metodo pubblico per prelevare denaro
  preleva(importo) {
    if (typeof importo !== "number" || importo <= 0) {
      throw new Error("L´importo da prelevare deve essere maggiore di zero");
    }

    if (importo > this.#saldo) {
      throw new Error("Fondi insufficienti");
    }

    this.#saldo -= importo;
    return this.#saldo;
  }

  // Metodo di utilità per mostrare i dati principali
  riepilogo() {
    return `${this.titolare}: saldo attuale €${this.#saldo}`;
  }
}

// Uso della classe
const conto = new ContoBancario("Luca", 100);

console.log(conto.riepilogo()); // Luca: saldo attuale €100
console.log(conto.deposita(50)); // 150
console.log(conto.preleva(30));  // 120

console.log(conto.saldo); // 120

// Il campo privato non è accessibile dall´esterno:
// console.log(conto.#saldo); // Errore di sintassi

// Il setter controlla i valori assegnati:
conto.saldo = 200;
console.log(conto.saldo); // 200

// Questo genererebbe un errore:
// conto.saldo = -10;

Spiegazione

Nel codice sopra abbiamo costruito una classe ContoBancario che rappresenta un caso d’uso reale: un oggetto con dati sensibili che non devono essere modificati liberamente dall’esterno.

1. Il campo privato #saldo

Il simbolo # davanti al nome della proprietà indica che si tratta di un campo privato. Questo significa che:

  • può essere letto e modificato solo all’interno della classe;
  • non è accessibile tramite notazione esterna, come conto.#saldo;
  • aiuta a proteggere l’integrità dello stato dell’oggetto.

Questo è molto importante quando vuoi evitare che altri pezzi di codice cambino il saldo in modo scorretto, ad esempio assegnando direttamente un valore negativo.

2. Il costruttore

Il constructor inizializza l’oggetto. Qui assegniamo il nome del titolare e verifichiamo che il saldo iniziale non sia negativo. Se il dato non è valido, generiamo un errore subito, così il problema emerge immediatamente.

3. Getter e setter

I getter e i setter sono metodi speciali che permettono di leggere e scrivere una proprietà come se fosse normale, ma con un controllo interno.

  • getter saldo(): restituisce il saldo attuale;
  • setter saldo(): valida il nuovo valore prima di assegnarlo.

Questo approccio è utile quando vuoi mantenere un’interfaccia semplice per chi usa la classe, ma allo stesso tempo applicare regole precise.

4. Metodi pubblici con regole di dominio

I metodi deposita e preleva rappresentano operazioni reali del conto. Entrambi controllano l’input prima di agire:

  • l’importo deve essere un numero valido;
  • deve essere maggiore di zero;
  • nel caso del prelievo, non si può superare il saldo disponibile.

Questo è un buon esempio di incapsulamento: l’oggetto non espone il “come” interno, ma solo il “cosa” può fare.

5. Perché non usare proprietà pubbliche?

Se usassi una proprietà pubblica come this.saldo, chiunque potrebbe modificarla in modo diretto:

conto.saldo = -9999;

In un sistema reale questo potrebbe causare bug difficili da individuare. Con i campi privati e i metodi di accesso, invece, centralizzi il controllo e riduci gli errori.

Best practice

  • Usa campi privati per lo stato interno critico: saldo, token, configurazioni sensibili, cache interne e dati che non devono essere alterati dall’esterno.
  • Valida sempre gli input: non fidarti dei valori passati ai metodi o ai setter, anche se provengono dal tuo stesso codice.
  • Esporre il minimo necessario: una classe ben progettata mostra solo le operazioni utili, non tutti i dettagli interni.
  • Preferisci metodi espliciti a modifiche dirette: ad esempio deposita() e preleva() sono più chiari di una semplice assegnazione a una proprietà.
  • Usa i getter con attenzione: sono utili per leggere dati calcolati o protetti, ma evita di creare accessori inutili che complicano l’API.
  • Lancia errori coerenti: messaggi chiari aiutano molto in fase di debug e rendono il comportamento della classe più prevedibile.
  • Non confondere privacy e sicurezza assoluta: i campi privati migliorano l’incapsulamento del codice, ma non sostituiscono misure di sicurezza applicative o lato server.

Riepilogo

L’incapsulamento è uno dei concetti più importanti della programmazione orientata agli oggetti, e in JavaScript moderno può essere implementato in modo elegante con campi privati, getter e setter.

In questo tutorial abbiamo visto come costruire una classe che protegge il proprio stato interno, come validare i dati in ingresso e come offrire un’interfaccia pubblica semplice e sicura. Questo stile di progettazione rende il codice più facile da mantenere, testare e riutilizzare.

Se vuoi scrivere applicazioni più solide, inizia a pensare alle tue classi non come semplici contenitori di dati, ma come oggetti responsabili del proprio comportamento e delle proprie regole.

Approfondisci con risorse ufficiali

  • MDN Web Docs - Classes: documentazione completa sulle classi in JavaScript.
  • MDN Web Docs - Private class features: spiegazione ufficiale di campi privati e metodi privati.
  • MDN Web Docs - Getter e Setter: guida all’uso degli accessori nelle classi e negli oggetti.
  • ECMAScript Language Specification: riferimento tecnico per chi vuole approfondire il comportamento del linguaggio.

SHARE