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_






