TypeScript - Classes Génériques

Bonjour, futurs superstars du codage ! Aujourd'hui, nous plongeons dans le monde passionnant des classes génériques de TypeScript. Ne vous inquiétez pas si vous êtes nouveau dans la programmation ; je vais vous guider à travers ce voyage étape par étape, comme j'ai fait pour des centaines d'étudiants au fil des ans. Alors, prenez votre boisson favorite, asseyez-vous confortablement, et embarquons dans cette aventure ensemble !

TypeScript - Generic Classes

Classes Génériques

Qu'est-ce que les Classes Génériques ?

Imaginez que vous êtes dans une crème glacée, mais au lieu de choisir des saveurs, vous choisissez des types de données. Voilà l'essence des classes génériques ! Elles nous permettent de créer des composants flexibles et réutilisables qui peuvent fonctionner avec différents types de données sans sacrifier la sécurité des types.

Commençons par un exemple simple :

class Box<T> {
private content: T;

constructor(value: T) {
this.content = value;
}

getValue(): T {
return this.content;
}
}

Dans cet exemple, Box est une classe générique. Le <T> est comme un placeholder pour un type que nous spécifierons plus tard. C'est comme dire à la crème glacée, "Je déciderai de la saveur lorsque je commanderais !"

Reprenons :

  • class Box<T> : Cela déclare une classe générique nommée Box avec un paramètre de type T.
  • private content: T : Nous disons que content sera de type T, peu importe ce que T sera.
  • constructor(value: T) : Le constructeur prend une valeur de type T.
  • getValue(): T : Cette méthode retourne une valeur de type T.

Maintenant, voyons comment nous pouvons utiliser cette classe :

let numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // Output: 42

let stringBox = new Box<string>("Bonjour, TypeScript !");
console.log(stringBox.getValue()); // Output: Bonjour, TypeScript !

C'est génial, non ? Nous avons utilisé la même classe Box pour stocker à la fois un nombre et une chaîne de caractères. C'est comme avoir une boîte magique qui peut contenir n'importe quoi que vous mettez dedans, mais se souvient toujours exactement du type de chose qu'elle contient !

Plusieurs Paramètres de Type

Parfois, un seul paramètre de type ne suffit pas. Créons un exemple plus complexe avec plusieurs paramètres de type :

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;
}
}

Cette classe Pair peut contenir deux valeurs de types potentiellement différents. C'est comme avoir un cornet de glace à deux boules où chaque boule peut être une saveur différente !

Utilisons notre classe Pair :

let pair = new Pair<string, number>("Âge", 30);
console.log(pair.getFirst());  // Output: Âge
console.log(pair.getSecond()); // Output: 30

Contraintes Génériques

Parfois, nous voulons limiter les types qui peuvent être utilisés avec notre classe générique. Nous pouvons le faire en utilisant des contraintes. C'est comme dire, "Vous pouvez avoir n'importe quelle saveur de glace, à condition qu'elle ne soit pas trop épicée !"

interface Lengthwise {
length: number;
}

class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `La longueur est : ${obj.length}`;
}
}

Dans cet exemple, T extends Lengthwise signifie que T doit être un type qui a une propriété length. Utilisons-le :

let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("Bonjour")); // Output: La longueur est : 6

let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // Output: La longueur est : 3

// Cela provoquerait une erreur :
// let numberChecker = new LengthChecker<number>();
// Type 'number' does not satisfy the constraint 'Lengthwise'.

Implémentation d'une Interface Générique avec une Classe Générique

Maintenant, levons nos compétences au niveau supérieur en implémentant une interface générique avec une classe générique. C'est comme créer une recette (interface) pour différents types de glace (classes) !

Premièrement, définissons une interface générique :

interface Repository<T> {
getById(id: number): T;
save(item: T): void;
}

Cette interface Repository définit un contrat pour les classes qui géreront le stockage et la récupération des données. Maintenant, implémentons cette interface avec une classe générique :

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);
}
}

Notre classe GenericRepository implémente l'interface Repository. Elle peut travailler avec n'importe quel type T. Utilisons-la :

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 }

Dans cet exemple, nous avons créé un dépôt pour des objets User. Mais la beauté de notre implémentation générique est que nous pourrions tout aussi bien créer un dépôt pour n'importe quel autre type !

Tableau des Méthodes

Voici un tableau pratique résumant les méthodes que nous avons couvertes :

Méthode Description Exemple
constructor(value: T) Crée une nouvelle instance d'une classe générique new Box<number>(42)
getValue(): T Retourne la valeur stockée dans une classe générique numberBox.getValue()
getFirst(): T Retourne la première valeur dans une paire pair.getFirst()
getSecond(): U Retourne la seconde valeur dans une paire pair.getSecond()
checkLength(obj: T): string Vérifie la longueur d'un objet (avec contraintes) stringChecker.checkLength("Bonjour")
getById(id: number): T Récupère un élément d'un dépôt par ID userRepo.getById(0)
save(item: T): void Enregistre un élément dans un dépôt userRepo.save({ name: "Alice", age: 30 })

Et voilà, les amis ! Nous avons parcouru le pays des classes génériques TypeScript, des boîtes simples aux dépôts complexes. Souvenez-vous, la pratique rend parfait, alors n'ayez pas peur d'expérimenter avec ces concepts. Qui sait ? Vous pourriez bien créer la prochaine grande chose en programmation ! Jusqu'à la prochaine fois, bon codage !

Credits: Image by storyset