Zum Inhalt
wannacode

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 Employee
public void Raise(int percent)
{
if (percent > 0)
{
Salary += Salary * percent / 100;
}
}
// in Manager: eine Kopie von vor der Prüfung
public void Raise(int percent)
{
Salary += Salary * percent / 100;
}
Employee ivan = new Employee("Ivan", 2000);
Manager maria = new Manager("Maria", 3000, 500);
ivan.Raise(-10);
maria.Raise(-10);
Console.WriteLine(ivan);
Console.WriteLine(maria);
Ausgabe
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 : Employee. In Manager schreibst du nur, was neu ist, den Bonus:

class Manager : Employee
{
public int Bonus { get; private set; }
public Manager(string name, int salary, int bonus) : base(name, salary)
{
Bonus = bonus;
}
}
Manager maria = new Manager("Maria", 3000, 500);
maria.Raise(-10);
maria.Raise(10);
Console.WriteLine(maria);
Console.WriteLine("Bonus: " + maria.Bonus + " €");
Ausgabe
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 außer einem Konstruktor nichts Eigenes:

Was gibt das Programm aus?

class Intern : Employee
{
public Intern(string name, int salary) : base(name, salary)
{
}
}
Intern ana = new Intern("Ana", 1200);
ana.Raise(5);
Console.WriteLine(ana);
Antwort

Die Idee

Die Basisklasse kommt zuerst

name und salary sind private in Employee. 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:

public Manager(string name, int salary, int bonus) : base(name, salary)
{
Bonus = bonus;
}

: base(name, salary) ruft den Konstruktor von Employee mit diesen Argumenten auf. Er richtet Name und Gehalt ein, und dann richtet der Rumpf des Konstruktors von Manager den Bonus ein. base schreibst du nach den Klammern des Konstruktors, vor den Rumpf.

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.

public Manager(string name, int salary, int bonus) : base(name, salary)
{
Bonus = bonus;
}
Manager maria = new Manager("Maria", 3000, 500);
Console.WriteLine(maria + ", Bonus " + maria.Bonus + " €");
Schritt für Schritt, in Worten
  1. new legt ein Objekt an. Es enthält den Teil von Employee und den Teil von Manager.
  2. Der Konstruktor von Manager startet mit den drei Argumenten.
  3. Die Basisklasse kommt zuerst: base(name, salary) ruft den Konstruktor von Employee auf, vor dem Rumpf.
  4. Der Konstruktor von Employee richtet seinen Teil ein: name und salary.
  5. Erst dann richtet der Rumpf von Manager den Bonus ein.
  6. Es gibt ein Objekt, und jeder Teil wird von seinem eigenen Konstruktor eingerichtet.
Genauer, für Neugierige
  • Konstruktoren werden nicht vererbt. Manager hat keinen Konstruktor Manager(string name, int salary), wenn du keinen schreibst.
  • Wenn die Basisklasse nur einen Konstruktor mit Parametern hat und die Unterklasse nichts übergibt, lässt sich das Programm nicht kompilieren. Der Compiler weiß nicht, welchen Namen und welches Gehalt er geben soll.

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
{
public Account()
{
Console.WriteLine("Das Konto ist eröffnet");
}
}
class SavingsAccount : Account
{
public SavingsAccount()
{
Console.WriteLine("Zinsen: 3%");
}
}
SavingsAccount savings = new SavingsAccount();
Antwort

Probier es aus

Finde den Fehler

Maria soll ein Gehalt von 3000 € und einen Bonus von 500 € haben. Aber das Programm gibt aus:

Ausgabe
Maria: 500 €

In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

protected: nur für die Unterklassen

Am Jahresende wird der Bonus des Managers zum Gehalt addiert. Die Methode steht in Manager, aber das Gehalt ist private in Employee. Ein Getter reicht nicht: Du musst es ändern.

Zwischen private und public gibt es eine dritte Zugriffsstufe: protected. Die Klasse und ihre Unterklassen sehen so ein Element, alle anderen nicht:

class Employee
{
public string Name { get; private set; }
public int Salary { get; protected set; }
// ... der Rest wie vorher
}
class Manager : Employee
{
// ... Bonus und der Konstruktor
public void MergeBonus()
{
Salary += Bonus;
Bonus = 0;
}
}
Manager maria = new Manager("Maria", 3000, 500);
maria.MergeBonus();
Console.WriteLine(maria);
Console.WriteLine("Bonus: " + maria.Bonus + " €");
Ausgabe
Maria: 3500 €
Bonus: 0 €

In C# öffnet man häufiger nicht die ganze Property, sondern nur den Setter: { get; protected set; }. Jeder kann das Gehalt lesen, aber nur Employee und seine Unterklassen können es ändern.

protected schwächt den Schutz: Jede Unterklasse kann das Gehalt ändern, ohne durch die Prüfung in Raise() zu gehen. Deshalb bleiben Felder standardmäßig private. protected ist für die Fälle, in denen die Unterklasse das Feld wirklich anfassen muss.

Genauer, für Neugierige
  • In C# ist protected nur für die Klasse und ihre Unterklassen. Es gibt auch protected internal: die Unterklassen plus alles im selben Projekt.

Probier es aus

Was fehlt?

Eine Praktikantin ist eine Mitarbeiterin. Was steht in der Lücke?

class Intern  Employee
{
    public Intern(string name, int salary) : base(name, salary)
    {
    }
}

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 : hat? Jede Klasse ohne Basisklasse erbt von object. Das ist die gemeinsame Basisklasse aller Klassen. Deshalb hat sogar eine leere Klasse Methoden:

class Ticket
{
}
Ticket a = new Ticket();
Ticket b = new Ticket();
Console.WriteLine(a.Equals(a));
Console.WriteLine(a.Equals(b));
Console.WriteLine(a.ToString());
Ausgabe
True
False
Ticket

Equals() kommt von object. Vorerst sagt es dir nur, ob zwei Variablen auf dasselbe Objekt zeigen.

Auch ToString() kommt von dort. Wenn du sie nicht schreibst, gibt sie den Namen der Klasse zurück, deshalb wird oben „Ticket“ ausgegeben. Im Modul „Klassen und Objekte“ hast du ein eigenes ToString() mit override geschrieben. Jetzt weißt du, dass es die Version von object ersetzt. Was override genau bedeutet, steht in der nächsten Lektion.

Genauer, für Neugierige
  • Von einer Klasse mit sealed vor class kann man nicht erben. string ist so eine.
  • object ist ein kurzer Name für System.Object. Eine Variable vom Typ object kann auf jedes beliebige Objekt zeigen: object anything = maria;.

Ü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:

Ausgabe
Geschwindigkeit: 10 km/h
Sitze: 5

2. 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.

Ausgabe
Ich bin Elena, 19 Jahre alt.
Notendurchschnitt: 5.5

3. Breakdance. DanceMove hat einen Namen, eine Dauer in Sekunden und Perform(). Schreib BreakDanceMove mit einer Schwierigkeit von 1 bis 10 und einer Methode ShowComplexity().

Ausgabe
Ich tanze moonwalk für 5 Sekunden
Ich tanze windmill für 12 Sekunden
windmill: Schwierigkeit 8 von 10

4. 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.
Die Kilometer sind private in Car. Wie ändert die Unterklasse sie? Tipp: die Karte über protected.
Ausgabe
Skoda: 150 km, Kraftstoff 35 l
Tesla: 200 km, Akku 60%
Der Akku reicht nicht für 400 km
Tesla: 600 km, Akku 20%

Weitere Aufgaben

  • Arbeiter. Worker erbt von Person und fügt eine Berufsbezeichnung und eine Methode Work() hinzu:
Ausgabe
Ich bin Peter, 34 Jahre alt.
Ich arbeite als Buchhalter
  • Praktikantin mit Mentor. Füge Intern ein Feld mit dem Namen des Mentors hinzu und übergib ihn im Konstruktor, zusammen mit Name und Gehalt für die Basisklasse.

Test

Teste dich

1. class Manager : Employee. Was stimmt?
2. Das Feld name ist private in Employee. Kann Manager in einer seiner Methoden name = "Maria"; schreiben?
3. Was macht : base(name, salary) im Konstruktor von Manager?
4. Wie viele Basisklassen kann eine Klasse haben?
5. Wer sieht ein protected-Feld?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·