TypeScript ergänzt JavaScript-Klassen um Typen, Sichtbarkeit und abstrakte Klassen.
Eigenschaften und Methoden
class Konto {
readonly inhaber: string;
#stand: number = 0; // echt privat (JavaScript)
private log: string[] = []; // nur zur Compile-Zeit privat (TypeScript)
constructor(inhaber: string, startwert = 0) {
this.inhaber = inhaber;
this.#stand = startwert;
}
einzahlen(betrag: number): this {
if (betrag <= 0) throw new RangeError("Betrag muss positiv sein");
this.#stand += betrag;
this.log.push(`+${betrag}`);
return this;
}
get stand(): number {
return this.#stand;
}
verlauf(): string[] {
return [...this.log];
}
}
const k = new Konto("Mia", 100).einzahlen(50).einzahlen(25);
console.log(k.inhaber, k.stand, k.verlauf());Mia 175 [ '+50', '+25' ]
| Modifikator | Wirkung |
|---|---|
public | überall sichtbar (Standard) |
private | nur in der Klasse (Compile-Zeit) |
protected | in der Klasse und Unterklassen |
readonly | nur im Konstruktor zuweisbar |
#name | echt privat zur Laufzeit |
static | gehört zur Klasse |
class A { private geheim = 1; }
const a = new A();
console.log(a.geheim);error TS2341: Property 'geheim' is private and only accessible within class 'A'.
Interfaces umsetzen
Eine Klasse kann einen Vertrag erfüllen müssen:
interface Form {
flaeche(): number;
readonly name: string;
}
class Kreis implements Form {
readonly name = "Kreis";
constructor(private radius: number) {}
flaeche(): number { return Math.PI * this.radius ** 2; }
}interface Form { flaeche(): number; readonly name: string }
class Rechteck implements Form {
readonly name = "Rechteck";
breite: number;
hoehe: number;
constructor(breite: number, hoehe: number) { this.breite = breite; this.hoehe = hoehe; }
flaeche(): number { return this.breite * this.hoehe; }
}
class Quadrat extends Rechteck {
constructor(seite: number) { super(seite, seite); }
}
const formen: Form[] = [new Rechteck(2, 3), new Quadrat(4)];
console.log(formen.map((f) => f.flaeche()));[ 6, 16 ]
Abstrakte Klassen
Eine abstrakte Klasse ist nur Vorlage und kann nicht direkt instanziiert werden:
abstract class Tier {
constructor(protected name: string) { }
abstract laut(): string; // Unterklassen müssen das umsetzen
vorstellen(): string { return `${this.name} macht ${this.laut()}`; }
}In reinem JavaScript-Stil, damit er auch ohne Compiler läuft:
abstract class Tier {
name: string;
constructor(name: string) { this.name = name; }
abstract laut(): string;
vorstellen(): string { return `${this.name} macht ${this.laut()}`; }
}
class Hund extends Tier { laut() { return "Wuff"; } }
class Katze extends Tier { laut() { return "Miau"; } }
for (const t of [new Hund("Rex"), new Katze("Mimi")]) console.log(t.vorstellen());Rex macht Wuff Mimi macht Miau
abstract class Tier { abstract laut(): string }
const t = new Tier();error TS2511: Cannot create an instance of an abstract class.
Statische Member und Getter/Setter
class Temperatur {
static readonly ABSOLUTER_NULLPUNKT = -273.15;
#celsius = 0;
get celsius(): number { return this.#celsius; }
set celsius(w: number) {
if (w < Temperatur.ABSOLUTER_NULLPUNKT) throw new RangeError("zu kalt");
this.#celsius = w;
}
get fahrenheit(): number { return this.#celsius * 9 / 5 + 32; }
static ausFahrenheit(f: number): Temperatur {
const t = new Temperatur();
t.celsius = (f - 32) * 5 / 9;
return t;
}
}
const t = Temperatur.ausFahrenheit(212);
console.log(t.celsius, t.fahrenheit);100 212
Klassen als Typ
Der Name einer Klasse ist zugleich ein Typ für ihre Instanzen:
class Punkt { constructor(public x = 0, public y = 0) {} }Auch hier gilt: strukturelle Typisierung. Ein Objekt { x: 1, y: 2 } passt zum Typ Punkt, obwohl es keine Instanz ist.
Composition over Inheritance
Vererbung koppelt Klassen eng. Oft besser ist Komposition: Eine Klasse bekommt andere Objekte über das Interface und nutzt sie.
interface Speicher { speichere(k: string, v: string): void; lade(k: string): string | undefined }
class MemSpeicher implements Speicher {
private daten = new Map<string, string>();
speichere(k: string, v: string) { this.daten.set(k, v); }
lade(k: string) { return this.daten.get(k); }
}
class Einstellungen {
speicher: Speicher;
constructor(speicher: Speicher) { this.speicher = speicher; } // Abhängigkeit hereingegeben
setzeThema(t: "hell" | "dunkel") { this.speicher.speichere("thema", t); }
thema() { return this.speicher.lade("thema") ?? "hell"; }
}
const e = new Einstellungen(new MemSpeicher());
console.log(e.thema());
e.setzeThema("dunkel");
console.log(e.thema());hell dunkel
Im Test kannst du jederzeit einen anderen Speicher hineingeben, im Browser etwa localStorage.
Merke
- Sichtbarkeit:
public,private,protected,readonly;#nameist echt privat implementserzwingt, dass eine Klasse ein Interface erfülltabstractfür Vorlagen;staticfür Klassenebene- Klassen sind strukturell typisiert
- Komposition und Schnittstellen halten Code flexibel und testbar
Aufgabe
Baue eine abstrakte Klasse Mitarbeiter mit gehalt() und zwei Unterklassen (Angestellter, Freelancer) und summiere die Gehälter einer gemischten Liste.