Abstrakte Klassen ohne abstract
Kurz gesagt: JavaScript hat kein abstract: Eine abstrakte Klasse ist eine Basisklasse, die new mit new.target aufhält und aus den Methoden, die die Unterklasse schreiben muss, einen Fehler wirft.
Problem
Null Quadratmeter
Du schreibst ein Programm, das Fliesen für eine Terrasse bestellt. Die Terrasse hat drei Teile: ein Rechteck von 4 × 3 m, einen runden Teil mit 1 m Radius und ein Dreieck mit 2 m Grundseite und 3 m Höhe. Es gibt eine Basisklasse Shape mit area(), die 0 zurückgibt, und jede Figur überschreibt sie.
Der Kollege, der Triangle geschrieben hat, hat es vergessen:
class Triangle extends Shape { #base; #height;
constructor(base, height) { super(); this.#base = base; this.#height = height; }}const terrace = [new Rectangle(4, 3), new Circle(1), new Triangle(2, 3)];let total = 0;for (const s of terrace) { total += s.area();}console.log(`Fliesen: ${total.toFixed(2)} m²`);Fliesen: 15.14 m²Du brauchst 18.14 m². Das Programm meldet keinen Fehler, und der Bestellung fehlen 3 m². Die Null der Basisklasse hat die fehlende Methode versteckt.
Und was ist new Shape()? Eine Figur ohne Form. So ein Objekt hat keinen Grund zu existieren.
Die Idee
Abstrakte Klasse: ein Versprechen, das die Unterklassen halten
Shape ist keine echte Figur. Sie sagt nur „jede Figur hat eine Fläche“. Also mach sie abstrakt:
class Shape { constructor() { if (new.target === Shape) { throw new Error("Shape ist abstrakt: Erzeuge eine konkrete Figur"); } }
area() { throw new Error("area() muss in der Unterklasse geschrieben werden"); }}JavaScript hat kein abstract. Die Basisklasse setzt die Regel selbst durch, mit zwei Prüfungen:
- Die „abstrakte“ Methode ist eine Methode der Basisklasse, die nichts Sinnvolles zurückgibt, sondern einen Fehler wirft:
throw new Error(...)hält das Programm an und sagt, warum. Mehr überthrowlernst du im Modul „Fehler und Exceptions“. - Die „abstrakte“ Klasse prüft in ihrem Konstruktor
new.target: die Klasse, deren Objekt gerade erzeugt wird. Beinew Shape()ist esShape, und der Konstruktor wirft einen Fehler. Beinew Circle(1)ist esCircle, und die Prüfung lässt es durch. - Deshalb rufen die Unterklassen in ihrem Konstruktor
super()auf, auch wenn sie nichts zu übergeben haben.
Nichts davon wird im Voraus geprüft. Das vergessene Triangle wird ohne Murren erzeugt. Aber wenn die Schleife bei seinem area() ankommt, bricht das Programm mit „Error: area() muss in der Unterklasse geschrieben werden“ ab, statt still 3 m² zu wenig zu bestellen.
Genauer, für Neugierige
new.targetfunktioniert nur in einem Konstruktor. Außerhalb ist esundefined.- TypeScript, also JavaScript mit Typen, hat ein echtes
abstractund prüft es, bevor das Programm startet. Reines JavaScript hat nichts dergleichen.
Sieh es dir an
Das Dreieck hält das Versprechen
Füg die Methode zu Triangle hinzu:
class Triangle extends Shape { // ... #base, #height und der Konstruktor
area() { return this.#base * this.#height / 2; }}const terrace = [new Rectangle(4, 3), new Circle(1), new Triangle(2, 3)];let total = 0;for (const s of terrace) { total += s.area();}console.log(`Fliesen: ${total.toFixed(2)} m²`);Fliesen: 18.14 m²Die Schleife ist dieselbe. Das Array enthält Figuren aus drei Klassen, und s.area() ruft die Methode jeder einzelnen auf. Die Polymorphie aus der letzten Lektion funktioniert auch mit einer abstrakten Basisklasse.
Probier es aus
Finde den Fehler
Wähle die Zeile mit dem Fehler.
new Shape() ist ein Versuch, ein Objekt einer abstrakten Klasse zu erzeugen. Die erste Zeile ist in Ordnung: new Array(3) erzeugt ein Array mit drei leeren Plätzen für Figuren, keine Figuren. Du kannst ein Rechteck, einen Kreis oder ein Dreieck hineinlegen. Der Konstruktor von Shape sieht, dass new.target Shape ist, und hält das Programm mit „Shape ist abstrakt: Erzeuge eine konkrete Figur“ an.
Die Idee
Abstrakt, aber nicht leer
Eine abstrakte Klasse kann alles haben, was eine normale hat: Felder, einen Konstruktor, normale Methoden. Jede Figur soll einen Namen und eine Methode haben, die sie beschreibt:
class Shape { #name;
constructor(name) { if (new.target === Shape) { throw new Error("Shape ist abstrakt: Erzeuge eine konkrete Figur"); } this.#name = name; }
area() { throw new Error("area() muss in der Unterklasse geschrieben werden"); }
describe() { return `${this.#name}: ${this.area().toFixed(2)} m²`; }}describe() wird einmal geschrieben, in Shape. Sie ruft area() auf, die in Shape nur einen Fehler wirft. Das ist kein Problem: describe() wird immer auf einer echten Figur aufgerufen, und eine echte Figur hat eine Fläche.
Von Shape erzeugst du keine Objekte, aber ihr Konstruktor ist nützlich: Die Unterklassen rufen ihn auf.
class Circle extends Shape { #radius;
constructor(radius) { super("Kreis"); this.#radius = radius; }
// ... area() wie vorher}const terrace = [new Rectangle(4, 3), new Circle(1), new Triangle(2, 3)];for (const s of terrace) { console.log(s.describe());}Rechteck: 12.00 m²Kreis: 3.14 m²Dreieck: 3.00 m²Die Basisklasse schreibt das Gemeinsame (den Namen, die Beschreibung), und die Unterklassen schreiben das Unterschiedliche (die Fläche).
area() { throw new Error("area() muss in der Unterklasse geschrieben werden");}
describe() { return `${this.#name}: ${this.area().toFixed(2)} m²`;}
const s = new Circle(1);console.log(s.describe());Schritt für Schritt, in Worten
- Shape „verspricht“ area(): Der Rumpf wirft nur einen Fehler. Es kann kein Objekt geben, das nur eine Shape ist.
- Das Objekt ist ein Kreis: Der Teil von Circle hat sein eigenes area(). Er hält das Versprechen.
- s.describe() ist in Shape geschrieben und funktioniert für jede Figur.
- Sie ruft area() auf. Das Objekt ist ein Kreis, also läuft die Methode von Circle: π · 1 · 1.
- describe() setzt den Text zusammen. Das Gemeinsame steht in der Basisklasse, das Unterschiedliche in der Unterklasse.
- Die Ausgabe ist die Fläche des Kreises, mit zwei Nachkommastellen.
Probier es aus
Wessen Methode?
Was gibt das Programm aus?
// Shape und Circle sind aus der Karte oben
const s = new Circle(2);console.log(s.describe());Das Objekt ist ein Kreis mit Radius 2. describe() gehört zu Shape, aber das area() darin ist das des Kreises: π · 2 · 2 ≈ 12.57.
Probier es aus
Was fehlt?
Was muss in die Lücke, damit man kein Objekt von Shape erzeugen kann?
class Shape {
constructor() {
if ( === Shape) {
throw new Error("Shape ist abstrakt: Erzeuge eine konkrete Figur");
}
}
area() {
throw new Error("area() muss in der Unterklasse geschrieben werden");
}
}new.target: die Klasse, deren Objekt gerade erzeugt wird. Nur bei new Shape() ist es Shape, also kommen die Unterklassen durch.Übung
Figuren und Tiere
Löse die Aufgaben in deinem Editor.
1. Fläche und Umfang. Eine abstrakte Klasse Shape mit einem Namen und zwei abstrakten Methoden: area() und perimeter(). Unterklassen Circle (Radius) und Rectangle (Breite und Höhe). Leg einen Kreis mit Radius 5 und ein Rechteck von 4 × 2.5 in ein Array und gib beide Werte mit zwei Nachkommastellen aus.
Kreis: Fläche 78.54, Umfang 31.42Rechteck: Fläche 10.00, Umfang 13.002. Tiere. Eine abstrakte Klasse Animal mit einer abstrakten Methode sound() und einer normalen Methode breathe(), die „Das Tier atmet.“ ausgibt. Unterklassen Cat und Dog. Ruf in einer Schleife den Laut beider auf, dann breathe() auf dem ersten.
Miau, miau!Wau, wau!Das Tier atmet.Übung
Fahrzeuge und Auftritte
3. Fahrzeuge. Eine abstrakte Klasse Vehicle mit einer abstrakten Methode move(). SimpleCar und Bicycle erben von ihr. Bicycle hat außerdem eine eigene Methode pedal().
Das Auto fährt losDas Fahrrad fährt losIch trete in die Pedalegarage[1].pedal(): Es funktioniert, weil dort ein Fahrrad steht. Und garage[0].pedal()? Warum bricht das Programm erst in dieser Zeile ab und nicht früher?4. Auftritte. Eine abstrakte Klasse Performance mit einem Titel und einer abstrakten Methode start(). DanceMove und Song erben von ihr. Song hat außerdem eine Methode sing().
Es beginnt der Tanz moonwalkEs beginnt das Lied 'Bohemian Rhapsody'Ich singe 'Bohemian Rhapsody'Weitere Aufgaben
- Dreieck. Füg Aufgabe 1 ein
Trianglemit seinen drei Seiten hinzu. Der Umfang ist ihre Summe, und die Fläche kommt aus der Formel von Heron: s = Umfang / 2, Fläche = √(s(s − a)(s − b)(s − c)). Bei den Seiten 3, 4 und 5 ist die Fläche 6.00 und der Umfang 12.00. - Abstrakter Mitarbeiter. Mach
Employeeaus der letzten Lektion abstrakt, mit einem abstraktenmonthlyPay(). FügFullTimehinzu (das Gehalt wird voll ausgezahlt) und behalteManagerundIntern. Was gewinnt das Programm? Was verliert es?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Instanzmethoden · Kapselung · Vererbung · Polymorphie
- Geht weiter in: Interfaces ohne interface · Typprüfungen · OOP-Prinzipien · Werfen und eigene Exceptions
- Aufgaben zum Thema: Vererbung und Polymorphie