Vererbung
Kurz gesagt: Vererbung bedeutet, dass eine Klasse (die Unterklasse) die Felder und Methoden einer anderen Klasse (der Basisklasse) bekommt, sodass das Gemeinsame an einer Stelle steht.
Problem
Zwei Kopien desselben Codes
Das Gehaltsprogramm hat eine Klasse Employee mit Name, Gehalt und einer Methode für eine Erhöhung. Dann brauchtest du auch einen Manager, der zusätzlich einen Bonus bekommt. Am schnellsten ging es, Employee in eine neue Klasse Manager zu kopieren und den Bonus hinzuzufügen.
Einen Monat später gibt jemand eine Erhöhung von -10% statt 10% ein. Also fügst du Employee eine Prüfung hinzu. In Manager vergisst du sie:
// in Employeeraise(percent) { if (percent > 0) { this.#salary += Math.trunc(this.#salary * percent / 100); }}
// in Manager: eine Kopie von vor der Prüfungraise(percent) { this.#salary += Math.trunc(this.#salary * percent / 100);}const ivan = new Employee("Ivan", 2000);const maria = new Manager("Maria", 3000, 500);ivan.raise(-10);maria.raise(-10);console.log(String(ivan));console.log(String(maria));Ivan: 2000 €Maria: 2700 €Das Gehalt von Ivan ist geschützt, aber das von Maria ist um 300 € gefallen. Die zwei Klassen sind zu 90% gleich, und trotzdem muss jede Korrektur zweimal gemacht werden.
Die Idee
Das Gemeinsame kommt in die Basisklasse
Ein Manager ist ein Mitarbeiter, nur mit etwas zusätzlich. Sag es im Code: class Manager extends Employee. In Manager schreibst du nur, was neu ist, den Bonus:
class Manager extends Employee { #bonus;
constructor(name, salary, bonus) { super(name, salary); this.#bonus = bonus; }
get bonus() { return this.#bonus; }}
const maria = new Manager("Maria", 3000, 500);maria.raise(-10);maria.raise(10);console.log(String(maria));console.log(`Bonus: ${maria.bonus} €`);Maria: 3300 €Bonus: 500 €Manager hat jetzt alles, was Employee hat: einen Namen, ein Gehalt, raise() mit der Prüfung, toString(). Deshalb ist die Erhöhung um -10% nicht durchgegangen. Die Prüfung steht einmal da und funktioniert für beide Klassen.
Das nennt man Vererbung. Employee ist die Basisklasse (Elternklasse), und Manager ist die Unterklasse. Die Unterklasse nimmt alles von der Basisklasse und fügt ihr Eigenes hinzu.
Ob Vererbung sinnvoll ist, prüfst du mit dem Satz „X ist ein Y“. „Ein Manager ist ein Mitarbeiter“ stimmt. „Eine Rechnung ist ein Mitarbeiter“ stimmt nicht, also erbt die Rechnung nicht von Employee, auch wenn sie einen Betrag und einen Namen hat.
Probier es aus
Was bekommt die Praktikantin?
Intern hat nichts Eigenes: Der Rumpf ist leer, {}.Was gibt das Programm aus?
class Intern extends Employee {}
const ana = new Intern("Ana", 1200);ana.raise(5);console.log(String(ana));raise() und toString() sind von Employee geerbt. 5% von 1200 sind 60, und 1200 + 60 = 1260. Auch der Konstruktor ist geerbt: Deshalb gibt new Intern("Ana", 1200) Name und Gehalt direkt an Employee weiter.
Die Idee
Die Basisklasse kommt zuerst
#name und #salary sind in Employee versteckt. Die Unterklasse hat sie, sieht sie aber nicht: Für sie sind sie versteckt, wie für jede andere Klasse. Wie gibt Manager ihnen dann einen Wert?
Indem er den Konstruktor der Basisklasse aufruft:
constructor(name, salary, bonus) { super(name, salary); this.#bonus = bonus;}super(name, salary) ruft den Konstruktor von Employee mit diesen Argumenten auf. Er richtet #name und #salary ein, und dann richtet Manager #bonus ein. In JavaScript muss super(...) aufgerufen werden, bevor der Konstruktor this anfasst. Wenn du es vergisst, bricht new Manager(...) mit ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor ab.
Es gibt ein Objekt. Es enthält den Teil der Basisklasse und den Teil der Unterklasse, und jeder Teil wird von seinem eigenen Konstruktor eingerichtet.
constructor(name, salary, bonus) { super(name, salary); this.#bonus = bonus;}
const maria = new Manager("Maria", 3000, 500);console.log(`${maria}, Bonus ${maria.bonus} €`);Schritt für Schritt, in Worten
- new erzeugt ein Objekt. Es wird den Teil von Employee und den Teil von Manager enthalten.
- Der Konstruktor von Manager startet mit den drei Argumenten.
- Die Basisklasse kommt zuerst: super(name, salary) ruft den Konstruktor von Employee auf. Davor darfst du this nicht anfassen.
- Der Konstruktor von Employee richtet seinen Teil ein: #name und #salary.
- Erst dann richtet der Rumpf von Manager den Bonus ein.
- Es gibt ein Objekt, und jeder Teil wird von seinem eigenen Konstruktor eingerichtet.
Genauer, für Neugierige
- Der Konstruktor wird vererbt. Deshalb nimmt
Internohne eigenenconstructorName und Gehalt vonEmployee: JavaScript ruft die Basisklasse für dich mit den übergebenen Argumenten auf. - Sobald du einen eigenen
constructorschreibst, istsuper(...)deine Aufgabe. Die Felder der Unterklasse (#bonus) werden erst angelegt, nachdemsuper(...)fertig ist.
Probier es aus
Welcher Konstruktor läuft zuerst?
Hier ruft die Unterklasse die Basisklasse nicht ausdrücklich auf:
Was gibt das Programm aus?
class Account { constructor() { console.log("Das Konto ist eröffnet"); }}
class SavingsAccount extends Account { constructor() { console.log("Zinsen: 3%"); }}
const savings = new SavingsAccount();constructor muss super() selbst aufrufen. Hier tut sie es nicht: Sie gibt „Zinsen: 3%“ aus, und beim Verlassen des Konstruktors bricht das Programm mit ReferenceError ab. Die Korrektur ist super(); in der ersten Zeile. Dann wird „Das Konto ist eröffnet“ zuerst ausgegeben.Probier es aus
Finde den Fehler
Maria soll ein Gehalt von 3000 € und einen Bonus von 500 € haben. Aber das Programm gibt aus:
Maria: 500 €In welcher Zeile ist der Fehler?
Wähle die Zeile mit dem Fehler.
Die Basisklasse bekommt den Bonus, nicht das Gehalt. Argumente werden den Parametern nach ihrer Position zugeordnet, also bekommt Employee 500 als Gehalt. Die Korrektur ist super(name, salary);.
Die Idee
Kein protected: ein Unterstrich
Am Jahresende wird der Bonus des Managers zum Gehalt addiert. Die Methode steht in Manager, aber das Gehalt ist #salary in Employee. Der Getter liest es nur, und hier musst du es ändern. Und this.#salary ist in Manager nicht erlaubt: Das Programm startet gar nicht erst, SyntaxError: Private field '#salary' must be declared in an enclosing class.
In Java und C# gibt es dafür eine dritte Zugriffsstufe, protected: Die Klasse und ihre Unterklassen sehen es, alle anderen nicht. JavaScript hat das nicht. Stattdessen schreibst du das Feld mit einem Unterstrich, _salary: eine Abmachung für „intern, für die Klasse und ihre Unterklassen“, die die Sprache nicht prüft:
class Employee { #name;
constructor(name, salary) { this.#name = name; this._salary = salary; } // ... der Rest wie vorher, mit this._salary}
class Manager extends Employee { // ... #bonus und der Konstruktor
mergeBonus() { this._salary += this.#bonus; this.#bonus = 0; }}const maria = new Manager("Maria", 3000, 500);maria.mergeBonus();console.log(String(maria));console.log(`Bonus: ${maria.bonus} €`);Maria: 3500 €Bonus: 0 €Die Abmachung ist kein Schutz: maria._salary = 0 im Programm geht auch durch, ohne Fehler. Deshalb bleibt alles, was die Unterklasse nicht anfassen soll, bei # (#name), und nur das Feld, das sie wirklich ändern muss, bekommt einen Unterstrich.
Genauer, für Neugierige
#ist echter Schutz: Das Feld ist weder von außen noch aus den Unterklassen erreichbar. Deshalb wird auch geprüft, bevor das Programm startet, nicht während es läuft.
Probier es aus
Was fehlt?
Eine Praktikantin ist eine Mitarbeiterin. Was steht in der Lücke?
class Intern Employee {}extends: Intern erweitert Employee.Die Idee
Eine Basisklasse und ein gemeinsamer Vorfahr
Eine Klasse hat genau eine Basisklasse. Sie kann nicht von Employee und Student zugleich erben. Deshalb ist die Vererbung einfach.
Die Kette kann lang sein. Intern erbt von Employee. Und wovon erbt Employee, wenn es kein extends hat? Jede Klasse ohne Basisklasse erbt von Object. Das ist die gemeinsame Basisklasse aller Klassen. Deshalb hat sogar eine leere Klasse Methoden:
class Ticket {}
const ticket = new Ticket();console.log(ticket.toString());console.log(ticket);[object Object]Ticket {}toString() kommt von Object und gibt [object Object] zurück. Im Modul „Klassen und Objekte“ hast du ein eigenes toString() geschrieben. Jetzt weißt du, dass es die Version von Object ersetzt. Mehr dazu in der nächsten Lektion.
console.log(ticket) ruft toString() nicht auf. Es zeigt die Klasse und die Felder: Ticket {}. Deshalb geben wir in dieser Lektion String(maria) aus: String() ruft toString() auf.
Genauer, für Neugierige
- In JavaScript gibt es keine Klasse, von der man nicht erben kann.
class WorkingStudent extends Employee, Studentstartet nicht:SyntaxError. Nachextendssteht genau eine Klasse.
Übung
Unterklassen
Löse die Aufgaben in deinem Editor. Die Basisklassen sind aus dem Modul „Klassen und Objekte“. Wenn du sie nicht zur Hand hast, schreib sie neu: Sie sind kurz.
1. Familienauto. SimpleCar hat eine Geschwindigkeit und eine Methode accelerate(), die sie um 10 erhöht. Schreib FamilyCar, das von SimpleCar erbt, mit 4 Sitzen beginnt und eine Methode addSeat() hat. Erzeuge ein Familienauto, beschleunige es und füge einen Sitz hinzu:
Geschwindigkeit: 10 km/hSitze: 52. Studentin. Person hat Name, Alter und introduce(). Schreib Student, das von Person erbt und einen Notendurchschnitt grade (eine Dezimalzahl) hinzufügt. Benutze den Konstruktor der Basisklasse.
Ich bin Elena, 19 Jahre alt.Notendurchschnitt: 5.53. Breakdance. DanceMove hat einen Namen, eine Dauer in Sekunden und perform(). Schreib BreakDanceMove mit einer Schwierigkeit von 1 bis 10 und einer Methode showComplexity().
Ich tanze moonwalk für 5 SekundenIch tanze windmill für 12 Sekundenwindmill: Schwierigkeit 8 von 104. Elektroauto. Car hat eine Marke, Kraftstoff in Litern und die gefahrenen Kilometer. refuel(liters) tankt nach, und drive(distance) verbraucht 1 Liter auf 10 km und addiert die Kilometer (reicht der Kraftstoff nicht, sagt die Methode es und fährt nicht). Schreib ElectricCar:
- ohne Kraftstoff und mit einem Akku, der bei 100% beginnt;
- mit einer Effizienz: wie viele km es mit 1% Akku fährt (5 beim „Tesla“);
charge()lädt den Akku auf 100%;driveElectric(distance)verbraucht Akku statt Kraftstoff und addiert ebenfalls die Kilometer.
Car #mileage sind, kann die Unterklasse sie nicht ändern. Tipp: die Karte über den Unterstrich.Skoda: 150 km, Kraftstoff 35 lTesla: 200 km, Akku 60%Der Akku reicht nicht für 400 kmTesla: 600 km, Akku 20%Weitere Aufgaben
- Arbeiter.
Workererbt vonPersonund fügt eine Berufsbezeichnung und eine Methodework()hinzu:
Ich bin Peter, 34 Jahre alt.Ich arbeite als Buchhalter- Praktikantin mit Mentor. Füge
Internein Feld mit dem Namen des Mentors hinzu und übergib ihn im Konstruktor, zusammen mit Name und Gehalt für die Basisklasse. Jetzt brauchtInterndoch ein eigenes constructor.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Klassen und Objekte · Konstruktoren und this · Instanzmethoden · Kapselung
- Geht weiter in: Polymorphie · Abstrakte Klassen ohne abstract · Interfaces ohne interface · Typprüfungen
- Aufgaben zum Thema: Vererbung und Polymorphie