TypeScript - Generische Klassen

Hallo, zukünftige Codingsuperstars! Heute tauchen wir in die aufregende Welt der TypeScript Generischen Klassen ein. Keine Sorge, wenn du neu im Programmieren bist; ich werde dich auf dieser Reise Schritt für Schritt führen, genau wie ich es in den letzten Jahren für unzählige Schüler getan habe. Also hole dir dein Lieblingsgetränk, setze dich bequem hin und lassen wir gemeinsam diese Abenteuerreise antreten!

TypeScript - Generic Classes

Generische Klassen

Was sind Generische Klassen?

Stell dir vor, du bist in einem Eiscreme Geschäft, aber anstatt Geschmacksrichtungen zu wählen, pickst du Datenarten aus. Das ist die Essenz von generischen Klassen! Sie erlauben es uns, flexible, wiederverwendbare Komponenten zu erstellen, die mit verschiedenen Datenarten arbeiten können, ohne die Typsicherheit zu opfern.

Lass uns mit einem einfachen Beispiel beginnen:

class Box<T> {
private content: T;

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

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

In diesem Beispiel ist Box eine generische Klasse. Das <T> ist wie ein Platzhalter für einen Typ, den wir später spezifizieren werden. Es ist, als würde man dem Eiscreme Geschäft sagen: "Ich entscheide mich für den Geschmack, wenn ich bestelle!"

Lass uns das aufschlüsseln:

  • class Box<T>: Das deklariert eine generische Klasse namens Box mit einem Typparameter T.
  • private content: T: Wir sagen, dass content vom Typ T sein wird, was auch immer T herauskommt.
  • constructor(value: T): Der Konstruktor nimmt einen Wert vom Typ T.
  • getValue(): T: Diese Methode gibt einen Wert vom Typ T zurück.

Nun schauen wir uns an, wie wir diese Klasse verwenden können:

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

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

Ist das nicht toll? Wir haben dieselbe Box Klasse verwendet, um sowohl eine Zahl als auch einen String zu speichern. Es ist, als hätte man eine magische Box, die alles aufnehmen kann, was man hineinlegt, aber immer noch genau weiß, welchen Typ von Ding sie enthält!

Mehrere Typparameter

Manchmal reicht ein Typparameter nicht aus. Lass uns ein komplexeres Beispiel mit mehreren Typparametern erstellen:

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

Diese Pair Klasse kann zwei Werte potenziell不同的 Typen aufnehmen. Es ist, als hätte man ein Duo-Eisbecher, bei dem jede Kugel einen anderen Geschmack haben kann!

Lassen wir unsere Pair Klasse verwenden:

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

Typ Constraints

Manchmal möchten wir einschränken, welche Typen mit unserer generischen Klasse verwendet werden können. Wir können das mit Constraints erreichen. Es ist, als würde man sagen: "Du kannst jede Eiscreme Geschmacksrichtung haben, solange sie nicht zu scharf ist!"

interface Lengthwise {
length: number;
}

class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `Die Länge beträgt: ${obj.length}`;
}
}

In diesem Beispiel bedeutet T extends Lengthwise, dass T ein Typ sein muss, der eine length Eigenschaft hat. Lassen wir es verwenden:

let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("Hallo")); // Ausgabe: Die Länge beträgt: 5

let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // Ausgabe: Die Länge beträgt: 3

// Dies würde einen Fehler verursachen:
// let numberChecker = new LengthChecker<number>();
// Type 'number' does not satisfy the constraint 'Lengthwise'.

Implementierung einer Generischen Schnittstelle mit Generischen Klassen

Nun nehmen wir unsere Fähigkeiten auf die nächste Stufe, indem wir eine generische Schnittstelle mit einer generischen Klasse implementieren. Es ist, als würde man ein Rezept (Schnittstelle) für verschiedene Eiscremearten (Klassen) erstellen!

Zuerst definieren wir eine generische Schnittstelle:

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

Diese Repository Schnittstelle definiert einen Vertrag für Klassen, die Daten Speicherung und Abrufung handhaben werden. Nun implementieren wir diese Schnittstelle mit einer generischen Klasse:

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

Unsere GenericRepository Klasse implementiert die Repository Schnittstelle. Sie kann mit jedem Typ T arbeiten. Lassen wir es verwenden:

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)); // Ausgabe: { name: "Alice", age: 30 }
console.log(userRepo.getById(1)); // Ausgabe: { name: "Bob", age: 25 }

In diesem Beispiel haben wir ein Repository für User Objekte erstellt. Aber die Schönheit unserer generischen Implementierung ist, dass wir genausogut ein Repository für jeden anderen Typ erstellen könnten!

Methodenübersicht

Hier ist eine praktische Tabelle, die die Methoden zusammenfasst, die wir behandelt haben:

Methode Beschreibung Beispiel
constructor(value: T) Erstellt eine neue Instanz einer generischen Klasse new Box<number>(42)
getValue(): T Gibt den im generischen Klasse gespeicherten Wert zurück numberBox.getValue()
getFirst(): T Gibt den ersten Wert in einem Paar zurück pair.getFirst()
getSecond(): U Gibt den zweiten Wert in einem Paar zurück pair.getSecond()
checkLength(obj: T): string Überprüft die Länge eines Objekts (mit Constraints) stringChecker.checkLength("Hallo")
getById(id: number): T Ein Objekt aus einem Repository nach ID abrufen userRepo.getById(0)
save(item: T): void Ein Objekt in ein Repository speichern userRepo.save({ name: "Alice", age: 30 })

Und das war's, Leute! Wir haben die Welt der TypeScript Generischen Klassen bereist, von einfachen.Boxen bis hin zu komplexen Repositories. Denken Sie daran, Übung macht den Meister, also fürchten Sie sich nicht, mit diesen Konzepten zu experimentieren. Wer weiß? Vielleicht erstellen Sie das nächste große Ding im Programmieren! Bis zum nächsten Mal, viel Spaß beim Coden!

Credits: Image by storyset