Cos'è l'Incapsulamento nella Programmazione Orientata agli Oggetti
L'incapsulamento è uno dei pilastri fondamentali della programmazione orientata agli oggetti (OOP) e rappresenta il principio di nascondere i dettagli implementativi di una classe, esponendo solo ciò che è necessario attraverso un'interfaccia pubblica ben definita. Questo concetto è cruciale per creare software manutenibile, sicuro e modulare.
🎯 Obiettivi dell'Incapsulamento
- Information Hiding: Nascondere i dettagli implementativi
- Protezione Dati: Prevenire accessi e modifiche non autorizzate
- Flessibilità: Cambiare implementazione senza impattare il codice esterno
- Manutenibilità: Ridurre l'accoppiamento tra componenti
I Modificatori di Accesso in TypeScript
TypeScript offre tre modificatori di accesso per implementare l'incapsulamento:
private, protected e public. La scelta del modificatore
giusto determina chi può accedere ai membri della classe.
1. Private: Accesso Solo Interno
I membri private sono accessibili solo all'interno della classe che li definisce.
Questo è il livello più restrittivo di incapsulamento e dovrebbe essere il default per tutti
i dati interni della classe.
class BankAccount {{ '{' }}
// Proprietà private - accesso solo interno
private balance: number;
private accountNumber: string;
private transactionHistory: Transaction[] = [];
private isLocked: boolean = false;
constructor(accountNumber: string, initialBalance: number) {{ '{' }}
this.accountNumber = accountNumber;
this.balance = initialBalance;
this.logTransaction('Account created', initialBalance);
{{ '}' }}
// Metodo pubblico con validazione
public deposit(amount: number): void {{ '{' }}
if (this.isLocked) {{ '{' }}
throw new Error('Account is locked');
{{ '}' }}
if (amount <= 0) {{ '{' }}
throw new Error('Amount must be positive');
{{ '}' }}
this.balance += amount;
this.logTransaction('Deposit', amount);
{{ '}' }}
public withdraw(amount: number): boolean {{ '{' }}
if (this.isLocked) {{ '{' }}
throw new Error('Account is locked');
{{ '}' }}
if (amount > this.balance) {{ '{' }}
console.log('Insufficient funds');
return false;
{{ '}' }}
if (amount <= 0) {{ '{' }}
throw new Error('Amount must be positive');
{{ '}' }}
this.balance -= amount;
this.logTransaction('Withdrawal', amount);
return true;
{{ '}' }}
// Getter pubblico - read-only access
public getBalance(): number {{ '{' }}
return this.balance;
{{ '}' }}
public getAccountNumber(): string {{ '{' }}
return this.accountNumber;
{{ '}' }}
// Metodo privato - helper interno
private logTransaction(type: string, amount: number): void {{ '{' }}
const transaction: Transaction = {{ '{' }}
id: Date.now().toString(),
type,
amount,
timestamp: new Date(),
balanceAfter: this.balance
{{ '}' }};
this.transactionHistory.push(transaction);
{{ '}' }}
// Metodo per ottenere copia della storia (non l'array originale!)
public getTransactionHistory(): readonly Transaction[] {{ '{' }}
return Object.freeze([...this.transactionHistory]);
{{ '}' }}
// Metodi amministrativi
public lockAccount(): void {{ '{' }}
this.isLocked = true;
{{ '}' }}
public unlockAccount(adminCode: string): void {{ '{' }}
if (this.verifyAdminCode(adminCode)) {{ '{' }}
this.isLocked = false;
{{ '}' }}
{{ '}' }}
private verifyAdminCode(code: string): boolean {{ '{' }}
// Logica di verifica sicura
return code === 'ADMIN_SECRET_CODE';
{{ '}' }}
{{ '}' }}
interface Transaction {{ '{' }}
id: string;
type: string;
amount: number;
timestamp: Date;
balanceAfter: number;
{{ '}' }}
// Utilizzo
const account = new BankAccount('IT60X0542811101000000123456', 1000);
account.deposit(500); // ✅ OK
console.log(account.getBalance()); // ✅ OK - 1500
// account.balance = 999999; // ❌ ERRORE: balance è private
// account.logTransaction(); // ❌ ERRORE: logTransaction è private
✨ Vantaggi dei Membri Private
- Controllo Totale: Solo la classe può modificare lo stato interno
- Validazione Centralizzata: Tutte le modifiche passano per metodi pubblici validati
- Refactoring Sicuro: Puoi cambiare implementazione privata senza rompere codice esterno
- Debugging Facile: Sai esattamente dove lo stato viene modificato
2. Protected: Accesso per Sottoclassi
I membri protected sono accessibili dalla classe e dalle sue sottoclassi.
Utili quando vuoi permettere l'estensione ma mantenere l'incapsulamento verso l'esterno.
abstract class Vehicle {{ '{' }}
protected engineStatus: 'on' | 'off' = 'off';
protected currentSpeed: number = 0;
protected readonly maxSpeed: number;
constructor(maxSpeed: number) {{ '{' }}
this.maxSpeed = maxSpeed;
{{ '}' }}
// Metodo protected - usabile da sottoclassi
protected startEngine(): void {{ '{' }}
if (this.engineStatus === 'off') {{ '{' }}
this.engineStatus = 'on';
console.log('Engine started');
{{ '}' }}
{{ '}' }}
protected stopEngine(): void {{ '{' }}
if (this.currentSpeed === 0) {{ '{' }}
this.engineStatus = 'off';
console.log('Engine stopped');
{{ '}' }} else {{ '{' }}
console.log('Cannot stop engine while moving');
{{ '}' }}
{{ '}' }}
// Metodo pubblico
public getSpeed(): number {{ '{' }}
return this.currentSpeed;
{{ '}' }}
// Metodo astratto - deve essere implementato
abstract accelerate(amount: number): void;
{{ '}' }}
class Car extends Vehicle {{ '{' }}
private gear: number = 1;
constructor() {{ '{' }}
super(200); // maxSpeed per auto
{{ '}' }}
accelerate(amount: number): void {{ '{' }}
// Posso accedere a membri protected del genitore
if (this.engineStatus === 'off') {{ '{' }}
this.startEngine(); // ✅ OK - protected method
{{ '}' }}
if (this.currentSpeed + amount <= this.maxSpeed) {{ '{' }}
this.currentSpeed += amount;
this.adjustGear();
{{ '}' }}
{{ '}' }}
brake(amount: number): void {{ '{' }}
this.currentSpeed = Math.max(0, this.currentSpeed - amount);
if (this.currentSpeed === 0) {{ '{' }}
this.stopEngine(); // ✅ OK - protected method
{{ '}' }}
{{ '}' }}
private adjustGear(): void {{ '{' }}
// Logica privata di cambio marcia
if (this.currentSpeed > 80) this.gear = 5;
else if (this.currentSpeed > 60) this.gear = 4;
else if (this.currentSpeed > 40) this.gear = 3;
else if (this.currentSpeed > 20) this.gear = 2;
else this.gear = 1;
{{ '}' }}
{{ '}' }}
// Utilizzo
const car = new Car();
car.accelerate(50);
console.log(car.getSpeed()); // 50
// car.startEngine(); // ❌ ERRORE: startEngine è protected
// car.engineStatus; // ❌ ERRORE: engineStatus è protected
3. Public: Accesso Libero
I membri public sono accessibili da qualsiasi parte del codice. Questi
costituiscono l'interfaccia pubblica della classe e dovrebbero essere
minimizzati e ben documentati.
⚠️ Principio del Minimo Privilegio
Inizia sempre con private e rilassa solo quando necessario. È più facile rendere qualcosa public in seguito che rendere privato qualcosa che era public (breaking change).
- Private by default
- Protected solo se necessario per sottoclassi
- Public solo per l'interfaccia essenziale
Getter e Setter: Accesso Controllato
I getter e setter in TypeScript offrono un modo elegante per controllare l'accesso alle proprietà, permettendo validazione e logica custom.
class User {{ '{' }}
private _email: string;
private _age: number;
private _passwordHash: string;
constructor(email: string, age: number, password: string) {{ '{' }}
this._email = email;
this._age = age;
this._passwordHash = this.hashPassword(password);
{{ '}' }}
// Getter - read-only access con trasformazione
get email(): string {{ '{' }}
return this._email.toLowerCase();
{{ '}' }}
// Setter con validazione
set email(value: string) {{ '{' }}
if (!this.isValidEmail(value)) {{ '{' }}
throw new Error('Invalid email format');
{{ '}' }}
this._email = value;
{{ '}' }}
// Getter
get age(): number {{ '{' }}
return this._age;
{{ '}' }}
// Setter con validazione
set age(value: number) {{ '{' }}
if (value < 0 || value > 150) {{ '{' }}
throw new Error('Invalid age');
{{ '}' }}
this._age = value;
{{ '}' }}
// Proprietà computed - solo getter
get isAdult(): boolean {{ '{' }}
return this._age >= 18;
{{ '}' }}
get ageGroup(): string {{ '{' }}
if (this._age < 18) return 'minor';
if (this._age < 65) return 'adult';
return 'senior';
{{ '}' }}
// Metodo pubblico per cambiare password
changePassword(oldPassword: string, newPassword: string): boolean {{ '{' }}
if (!this.verifyPassword(oldPassword)) {{ '{' }}
return false;
{{ '}' }}
if (newPassword.length < 8) {{ '{' }}
throw new Error('Password must be at least 8 characters');
{{ '}' }}
this._passwordHash = this.hashPassword(newPassword);
return true;
{{ '}' }}
// Metodi privati - helper
private isValidEmail(email: string): boolean {{ '{' }}
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
return emailRegex.test(email);
{{ '}' }}
private hashPassword(password: string): string {{ '{' }}
// Simulazione hashing (usa bcrypt in produzione!)
return `hash_${{ '{' }}password.split('').reverse().join(''){{ '}' }}`;
{{ '}' }}
private verifyPassword(password: string): boolean {{ '{' }}
return this.hashPassword(password) === this._passwordHash;
{{ '}' }}
{{ '}' }}
// Utilizzo
const user = new User('john@example.com', 25, 'password123');
console.log(user.email); // "john@example.com"
console.log(user.isAdult); // true
console.log(user.ageGroup); // "adult"
user.age = 30; // ✅ OK - passa validazione
// user.age = -5; // ❌ Errore: Invalid age
// user.email = 'invalid'; // ❌ Errore: Invalid email format
user.changePassword('password123', 'newSecurePass123'); // ✅ OK
// user._passwordHash; // ❌ ERRORE: _passwordHash è private
✅ Quando Usare Getter/Setter:
- Serve validazione dei dati
- Trasformazione del valore
- Proprietà computed
- Logging delle modifiche
- Lazy initialization
❌ Quando NON Usarli:
- Semplice accesso senza logica
- Solo per "sembrare OOP"
- Calcoli pesanti nel getter
- Side effects nel getter
Readonly: Immutabilità
Il modificatore readonly garantisce che una proprietà possa essere
assegnata solo durante l'inizializzazione (costruttore) e non possa più essere modificata.
class Configuration {{ '{' }}
public readonly appName: string;
public readonly version: string;
private readonly apiKey: string;
public readonly createdAt: Date;
constructor(appName: string, version: string, apiKey: string) {{ '{' }}
this.appName = appName;
this.version = version;
this.apiKey = apiKey;
this.createdAt = new Date(); // Solo qui può essere assegnato
{{ '}' }}
// Shorthand syntax nel costruttore
constructor(
public readonly appName: string,
public readonly version: string,
private readonly apiKey: string
) {{ '{' }}
// TypeScript crea automaticamente le proprietà
{{ '}' }}
getApiKey(): string {{ '{' }}
return this.apiKey;
{{ '}' }}
{{ '}' }}
const config = new Configuration('MyApp', '1.0.0', 'secret_key');
console.log(config.appName); // "MyApp"
// config.appName = 'NewName'; // ❌ ERRORE: readonly property
Encapsulation Best Practices
🎯 Linee Guida Professionali
1. Tell, Don't Ask
Invece di chiedere lo stato e poi decidere, dì all'oggetto cosa fare:
❌ Male (Ask):
if (account.getBalance() > amount) {{ '{' }}
account.setBalance(
account.getBalance() - amount
);
{{ '}' }}
✅ Bene (Tell):
account.withdraw(amount);
2. Law of Demeter (LoD)
Non parlare con gli sconosciuti - un oggetto dovrebbe chiamare solo:
- Metodi di se stesso
- Parametri passati al metodo
- Oggetti che crea
- Componenti diretti
❌ Violazione LoD:
user.getAddress()
.getCity()
.getZipCode()
✅ Rispetta LoD:
user.getCityZipCode()
3. Immutabilità quando Possibile
Favorisci oggetti immutabili - più facili da ragionare e thread-safe:
class Point {{ '{' }}
constructor(
public readonly x: number,
public readonly y: number
) {{ '{' }}{{ '}' }}
// Invece di modificare, ritorna nuovo oggetto
move(dx: number, dy: number): Point {{ '{' }}
return new Point(this.x + dx, this.y + dy);
{{ '}' }}
{{ '}' }}
Errori Comuni da Evitare
⚠️ Anti-Pattern dell'Incapsulamento
1. Getter/Setter Inutili (JavaBeans Anti-Pattern)
// ❌ Male - non aggiunge valore
class Product {{ '{' }}
private name: string;
getName(): string {{ '{' }} return this.name; {{ '}' }}
setName(name: string) {{ '{' }} this.name = name; {{ '}' }}
{{ '}' }}
// ✅ Meglio - proprietà pubblica se non serve logica
class Product {{ '{' }}
public name: string;
{{ '}' }}
2. Esporre Collezioni Mutabili
// ❌ Male - array può essere modificato esternamente
getItems(): Item[] {{ '{' }}
return this.items;
{{ '}' }}
// ✅ Bene - ritorna copia immutabile
getItems(): readonly Item[] {{ '{' }}
return Object.freeze([...this.items]);
{{ '}' }}
3. Troppi Getter/Setter
Se hai 20 getter e setter, probabilmente la classe fa troppe cose (viola Single Responsibility).
Conclusione
L'incapsulamento è fondamentale per creare codice OOP di qualità. Nascondendo i dettagli implementativi e esponendo solo interfacce ben definite, ottieni codice più sicuro, manutenibile e flessibile. Ricorda: private by default, e rilassa solo quando strettamente necessario.
🎯 Punti Chiave
- Usa
privateper tutti i dettagli interni - Usa
protectedsolo per estensione controllata - Mantieni l'interfaccia pubblica minima e stabile
- Valida sempre i dati in input
- Preferisci getter/setter quando serve logica
- Usa
readonlyper dati che non devono cambiare - Applica "Tell, Don't Ask" e Law of Demeter
📚 Prossimo nella Serie OOP
Nel prossimo articolo esploreremo Ereditarietà e Polimorfismo, vedendo come le classi possono estendersi e come sfruttare il polimorfismo per codice flessibile ed estensibile.
Domande frequenti
Cos'è l'incapsulamento nella programmazione orientata agli oggetti?
L'incapsulamento è uno dei pilastri fondamentali della programmazione orientata agli oggetti (OOP) e rappresenta il principio di nascondere i dettagli implementativi di una classe, esponendo solo ciò che è necessario attraverso un'interfaccia pubblica ben definita.
Quali sono i modificatori di accesso disponibili in TypeScript per implementare l'incapsulamento?
I modificatori di accesso disponibili in TypeScript per implementare l'incapsulamento sono private, protected e public.
Che cosa significa rendere una proprietà readonly in TypeScript?
Rendendo una proprietà readonly in TypeScript si garantisce che essa possa essere assegnata solo durante l'inizializzazione (costruttore) e non possa più essere modificata.
Perché è importante utilizzare l'incapsulamento nella programmazione orientata agli oggetti?
L'incapsulamento è fondamentale per creare codice OOP di qualità, poiché nasconde i dettagli implementativi e esponendo solo interfacce ben definite, ottieni codice più sicuro, manutenibile e flessibile.

Commenti
Caricamento commenti...
Accedi per commentare