Costruttori

I costruttori in TypeScript rappresentano metodi speciali responsabili dell’inizializzazione delle istanze di classe. Questi metodi vengono invocati automaticamente durante la creazione di nuovi oggetti e forniscono il meccanismo principale per configurare lo stato iniziale e stabilire le dipendenze necessarie per il corretto funzionamento dell’istanza.
Funzione e Responsabilità
Il costruttore ha la responsabilità fondamentale di trasformare una classe da template astratto a istanza concreta utilizzabile. Durante questo processo, deve inizializzare tutte le proprietà necessarie, validare i parametri di input, e stabilire qualsiasi relazione o dipendenza che l’oggetto richiede per operare correttamente.
TypeScript garantisce che tutte le proprietà non-optional siano inizializzate prima che l’istanza diventi utilizzabile. Questo controllo compile-time previene errori runtime dovuti all’accesso a proprietà non inizializzate, migliorando significativamente la robustezza del codice.
class User {
readonly id: string;
name: string;
email: string;
private createdAt: Date;
constructor(name: string, email: string) {
this.id = Math.random().toString(36);
this.name = name;
this.email = email;
this.createdAt = new Date();
}
}
Parametri e Tipizzazione
I parametri del costruttore seguono le stesse regole di tipizzazione delle funzioni TypeScript, supportando tipi primitivi, oggetti complessi, union types, e generics. Possono essere optional, avere valori di default, e utilizzare destructuring per parametri oggetto complessi.
La tipizzazione dei parametri del costruttore è cruciale per garantire che le istanze vengano create con dati validi e completi. TypeScript verifica a compile-time che tutti i parametri richiesti siano forniti e che i loro tipi siano compatibili con le aspettative della classe.
I parametri optional permettono flessibilità nella creazione di istanze, mentre i valori di default forniscono comportamenti sensati quando certi parametri non vengono specificati esplicitamente.
Parameter Properties
TypeScript offre una sintassi concisa chiamata parameter properties che permette di dichiarare e inizializzare proprietà di classe direttamente nei parametri del costruttore. Questo pattern riduce significativamente il boilerplate code eliminando la necessità di dichiarare separatamente le proprietà e assegnarle nel corpo del costruttore.
Utilizzando modificatori di accesso (public, private, protected) o readonly sui parametri del costruttore, TypeScript automaticamente crea le proprietà corrispondenti e le inizializza con i valori dei parametri.
class Product {
constructor(
public readonly id: string,
public name: string,
private price: number,
protected category: string
) {
// Le proprietà sono automaticamente create e inizializzate
}
getPrice(): number {
return this.price;
}
}
Overloading e Signature Multiple
TypeScript supporta constructor overloading attraverso la definizione di signature multiple che permettono diversi modi di costruire istanze della stessa classe. Questo meccanismo è utile quando la classe può essere inizializzata con set di parametri diversi o formati di dati alternativi.
L’overloading del costruttore richiede una implementazione che gestisca tutti i casi definiti nelle signature. TypeScript verifica che l’implementazione sia compatibile con tutte le signature dichiarate, garantendo type safety across tutte le varianti di costruzione.
Validazione e Error Handling
I costruttori sono il punto ideale per implementare validazione dei dati di input e error handling per scenari di inizializzazione invalida. La validazione nel costruttore garantisce che oggetti mal formati non possano essere creati, mantenendo invarianti di classe sin dalla creazione.
TypeScript supporta throwing di eccezioni tipizzate nel costruttore, permettendo gestione granulare degli errori di inizializzazione. Custom error classes possono fornire informazioni specifiche sui failure di validazione.
class BankAccount {
constructor(
public readonly accountNumber: string,
public readonly owner: string,
private initialBalance: number = 0
) {
if (!accountNumber || accountNumber.length < 10) {
throw new Error("Invalid account number");
}
if (initialBalance < 0) {
throw new Error("Initial balance cannot be negative");
}
}
}
Dependency Injection
I costruttori sono il meccanismo principale per dependency injection in TypeScript, permettendo alle classi di ricevere le dipendenze necessarie senza crearle internamente. Questo pattern migliora testability, maintainability, e flessibilità del codice.
Le dipendenze possono essere interfacce o classi concrete, con TypeScript che garantisce compatibilità di tipo. L’injection di interfacce piuttosto che implementazioni concrete facilita testing attraverso mock objects e promuove loose coupling.
Ereditarietà e Super
Nelle classi che estendono altre classi, il costruttore deve chiamare super() per inizializzare la classe padre prima di accedere a this. TypeScript enforced questa regola a compile-time, prevenendo errori di inizializzazione incompleta.
La chiamata a super() deve avvenire prima di qualsiasi accesso a this e deve fornire tutti i parametri richiesti dal costruttore della classe padre. Questo garantisce che l’ereditarietà mantenga corretti ordini di inizializzazione.
class Employee extends User {
constructor(
name: string,
email: string,
public department: string,
public salary: number
) {
super(name, email); // Deve essere chiamato per primo
if (salary <= 0) {
throw new Error("Salary must be positive");
}
}
}
Factory Pattern Integration
I costruttori possono essere combinati con factory methods per fornire alternative di costruzione più espressive e flessibili. I factory methods sono spesso implementati come static methods che chiamano il costruttore con parametri processati o derivati.
Questo pattern è particolarmente utile quando la logica di costruzione è complessa o quando si vogliono fornire multiple “flavors” di costruzione per la stessa classe, ciascuna con semantiche specifiche.
Performance Considerations
Le operazioni nel costruttore dovrebbero essere efficienti poiché vengono eseguite per ogni istanza creata. Operazioni costose come I/O, network calls, o calcoli complessi dovrebbero essere evitate o posticipate fino a quando non sono effettivamente necessarie.
L’inizializzazione lazy può essere implementata attraverso getter che inizializzano proprietà al primo accesso, bilanciando performance di costruzione con utilizzo di memoria.
I costruttori TypeScript forniscono quindi un meccanismo robusto e type-safe per l’inizializzazione di oggetti, combinando flessibilità di design con garanzie di correttezza che prevengono categorie intere di errori runtime comuni in JavaScript.