TypeScript - Classi Generiche
Ciao, futuri superstar del coding! Oggi ci immergeremo nel mondo emozionante delle Classi Generiche di TypeScript. Non preoccuparti se sei nuovo alla programmazione; ti guiderò in questo viaggio passo-passo, proprio come ho fatto per innumerevoli studenti durante gli anni della mia insegnanza. Allora, prendi la tua bevanda preferita, mettiti comodo e partiamo insieme questa avventura!
Classi Generiche
Cos'è una Classe Generica?
Immagina di essere in una gelateria, ma invece di scegliere gusti, stai scegliendo tipi di dati. Questo è l'essenza delle classi generiche! Loro ci permettono di creare componenti flessibili e riutilizzabili che possono lavorare con diversi tipi di dati senza sacrificare la sicurezza dei tipi.
Iniziamo con un esempio semplice:
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
In questo esempio, Box
è una classe generica. <T>
è come un placeholder per un tipo che specificheremo più tardi. È come dire alla gelateria, "Deciderò il gusto quando farò l'ordine!"
Ecco una spiegazione dettagliata:
-
class Box<T>
: Questa dichiara una classe generica chiamataBox
con un parametro di tipoT
. -
private content: T
: Stiamo dicendo checontent
sarà di tipoT
, qualunque siaT
. -
constructor(value: T)
: Il costruttore accetta un valore di tipoT
. -
getValue(): T
: Questo metodo restituisce un valore di tipoT
.
Ora vediamo come possiamo usare questa classe:
let numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // Output: 42
let stringBox = new Box<string>("Ciao, TypeScript!");
console.log(stringBox.getValue()); // Output: Ciao, TypeScript!
Non è fantastico? Abbiamo usato la stessa classe Box
per memorizzare sia un numero che una stringa. È come avere una scatola magica che può contenere qualsiasi cosa tu ci metti, ma che ricorda esattamente che tipo di cosa sta contenendo!
Parametri di Tipo Multipli
A volte, un solo parametro di tipo non è sufficiente. Creiamo un esempio più complesso con parametri di tipo multipli:
class Pair<T, U> {
private first: T;
private second: U;
constructor(first: T, second: U) {
this.first = first;
this.second = second;
}
getFirst(): T {
return this.first;
}
getSecond(): U {
return this.second;
}
}
Questa classe Pair
può contenere due valori di tipi potenzialmente diversi. È come avere un cono gelato a due gusti dove ogni cucchiaio può essere un gusto diverso!
Usiamo la nostra classe Pair
:
let pair = new Pair<string, number>("Età", 30);
console.log(pair.getFirst()); // Output: Età
console.log(pair.getSecond()); // Output: 30
vincoli di Generici
A volte, vogliamo limitare i tipi che possono essere usati con la nostra classe generica. Possiamo farlo usando vincoli. È come dire, "Puoi avere qualsiasi gusto di gelato, a patto che non sia troppo piccante!"
interface Lengthwise {
length: number;
}
class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `La lunghezza è: ${obj.length}`;
}
}
In questo esempio, T extends Lengthwise
significa che T
deve essere un tipo che ha una proprietà length
. Usiamolo:
let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("Ciao")); // Output: La lunghezza è: 5
let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // Output: La lunghezza è: 3
// Questo causerebbe un errore:
// let numberChecker = new LengthChecker<number>();
// Type 'number' does not satisfy the constraint 'Lengthwise'.
Implementazione di Interfaccia Generica con Classi Generiche
Ora, portiamo le nostre competenze al livello successivo implementando un'interfaccia generica con una classe generica. È come creare una ricetta (interfaccia) per diversi tipi di gelato (classi)!
Prima, definiamo un'interfaccia generica:
interface Repository<T> {
getById(id: number): T;
save(item: T): void;
}
Questa interfaccia Repository
definisce un contratto per le classi che gestiranno la memorizzazione e il recupero dei dati. Ora implementiamo questa interfaccia con una classe generica:
class GenericRepository<T> implements Repository<T> {
private items: T[] = [];
getById(id: number): T {
return this.items[id];
}
save(item: T): void {
this.items.push(item);
}
}
La nostra classe GenericRepository
implements l'interfaccia Repository
. Può lavorare con qualsiasi tipo T
. Usiamolo:
interface User {
name: string;
age: number;
}
let userRepo = new GenericRepository<User>();
userRepo.save({ name: "Alice", age: 30 });
userRepo.save({ name: "Bob", age: 25 });
console.log(userRepo.getById(0)); // Output: { name: "Alice", age: 30 }
console.log(userRepo.getById(1)); // Output: { name: "Bob", age: 25 }
In questo esempio, abbiamo creato un repository per oggetti User
. Ma la bellezza della nostra implementazione generica è che potremmo altrettanto facilmente creare un repository per qualsiasi altro tipo!
Tabella dei Metodi
Ecco una tabella utile che riassume i metodi trattati:
Metodo | Descrizione | Esempio |
---|---|---|
constructor(value: T) |
Crea una nuova istanza di una classe generica | new Box<number>(42) |
getValue(): T |
Restituisce il valore memorizzato in una classe generica | numberBox.getValue() |
getFirst(): T |
Restituisce il primo valore in una coppia | pair.getFirst() |
getSecond(): U |
Restituisce il secondo valore in una coppia | pair.getSecond() |
checkLength(obj: T): string |
Controlla la lunghezza di un oggetto (con vincoli) | stringChecker.checkLength("Ciao") |
getById(id: number): T |
Recupera un oggetto da un repository per ID | userRepo.getById(0) |
save(item: T): void |
Salva un oggetto in un repository | userRepo.save({ name: "Alice", age: 30 }) |
Ecco fatto, gente! Abbiamo viaggiato attraverso il territorio delle Classi Generiche di TypeScript, dalle semplici scatole ai complessi repository. Ricorda, la pratica fa la perfezione, quindi non aver paura di sperimentare con questi concetti. Chi lo sa? Potresti creare la prossima grande cosa nella programmazione! Arrivederci e Grazie, happy coding!
Credits: Image by storyset