Zum Inhalt
wannacode

Polymorphie

Kurz gesagt: Polymorphie bedeutet, dass ein Aufruf die Version der Methode ausführt, die zum echten Objekt gehört, nicht zum Typ der Variable; die Unterklasse überschreibt sie (override).

Problem

Das Gehalt mit einem if für jede Art

Die Firma hat normale Mitarbeiter, Manager mit Bonus und Praktikanten in Teilzeit. Jemand hat eine Klasse Employee mit einem Feld „Rolle“ gemacht, und das Gehalt wird so berechnet:

static int MonthlyPay(Employee e)
{
if (e.Role == "Manager")
{
return e.Salary + e.Bonus;
}
else if (e.Role == "Praktikant")
{
return e.Salary / 2;
}
return e.Salary;
}

Es funktioniert. Aber dann kommt eine neue Art von Mitarbeiter dazu: eine Honorarkraft. Du brauchst ein neues else if hier, und in der Methode für die Steuern, und in der Methode für den Lohnzettel, und überall, wo jemand nach der Rolle gefragt hat. Und jeder Mitarbeiter trägt ein Feld für den Bonus mit sich, obwohl nur Manager einen Bonus bekommen.

Der Code für den Manager ist über das ganze Programm verstreut, statt im Manager zu stehen.

Die Idee

Jede Klasse entscheidet selbst

Mit der Vererbung aus der letzten Lektion sind Manager und Intern eigene Klassen. Lass jede selbst sagen, wie hoch ihr Monatsgehalt ist. Die Basisklasse hat eine allgemeine Version, und die Unterklasse schreibt ihre eigene Version derselben Methode:

class Employee
{
// ... Name, Salary und der Konstruktor
public virtual int MonthlyPay()
{
return Salary;
}
}
class Manager : Employee
{
// ... Bonus und der Konstruktor
public override int MonthlyPay()
{
return Salary + Bonus;
}
}
Employee ivan = new Employee("Ivan", 2000);
Manager maria = new Manager("Maria", 3000, 500);
Console.WriteLine(ivan.MonthlyPay());
Console.WriteLine(maria.MonthlyPay());
Ausgabe
2000
3500

Das nennt man Überschreiben einer Methode (override). Die Unterklasse schreibt eine Methode mit demselben Namen, denselben Parametern und demselben Rückgabetyp. Für Objekte der Unterklasse ersetzt ihre Version die der Basisklasse.

In C# müssen beide Seiten einverstanden sein. Die Basisklasse sagt mit virtual, dass die Methode überschrieben werden darf. Die Unterklasse sagt mit override, dass sie sie überschreibt. Ohne virtual in der Basisklasse lässt sich override nicht kompilieren.

Genauer, für Neugierige
  • Schreibt die Unterklasse eine Methode mit demselben Namen ohne override, warnt der Compiler, dass die Methode die der Basisklasse verdeckt. Wenn du das wirklich willst, schreibst du new. Aber eine verdeckte Methode ist nicht überschrieben: Über eine Variable vom Typ der Basisklasse wird die Version der Basisklasse aufgerufen.
class Report
{
public virtual string Title()
{
return "Bericht";
}
}
class SalesReport : Report
{
public new string Title()
{
return "Verkaufsbericht";
}
}
SalesReport sales = new SalesReport();
Report report = sales;
Console.WriteLine(sales.Title());
Console.WriteLine(report.Title());
Ausgabe
Verkaufsbericht
Bericht
  • sealed override sagt „Klassen weiter unten dürfen sie nicht noch einmal ändern“.
  • Die Unterklasse kann die Methode nicht stärker verstecken: Ist sie in der Basisklasse public, bleibt sie public.

Probier es aus

Was hat sich geändert?

Die Praktikantin überschreibt nur die Methode für das Monatsgehalt:

Was gibt das Programm aus?

class Intern : Employee
{
public Intern(string name, int salary) : base(name, salary)
{
}
public override int MonthlyPay()
{
return Salary / 2;
}
}
Intern ana = new Intern("Ana", 1600);
Console.WriteLine(ana.MonthlyPay());
Console.WriteLine(ana.Salary);
Antwort

Die Idee

Ein Array für alle

Ein Manager ist ein Mitarbeiter. Also kann eine Variable vom Typ Employee auf ein Objekt vom Typ Manager zeigen, und ein Array Employee[] kann jede Art von Mitarbeiter enthalten:

Employee[] staff =
{
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Intern("Ana", 1600)
};
foreach (Employee e in staff)
{
Console.WriteLine(e.Name + ": " + e.MonthlyPay() + " €");
}
Ausgabe
Ivan: 2000 €
Maria: 3500 €
Ana: 800 €

Die Schleife ruft dieselbe Methode auf, e.MonthlyPay(), und das Ergebnis ist verschieden. Es läuft die Version des Objekts, nicht die des Typs der Variable. Das nennt man Polymorphie: griechisch für „Vielgestaltigkeit“. Ein Aufruf, und jedes Objekt antwortet auf seine Weise.

Jetzt ist eine neue Art von Mitarbeiter eine neue Klasse mit eigenem MonthlyPay(). Die Schleife und der Rest des Programms bleiben, wie sie sind. Es gibt kein if auf die Rolle und kein Bonusfeld bei Mitarbeitern, die keinen Bonus bekommen.

Der Typ der Variable entscheidet aber, was du aufrufen kannst. Über e erreichst du den Bonus nicht, auch wenn das Objekt ein Manager ist: Der Compiler weiß nur, dass e irgendein Employee ist. Wie du an Dinge kommst, die nur manche Objekte haben, steht in der Lektion „Typprüfungen“.

Sieh es dir an

Welche Methode läuft

Employee[] staff =
{
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Intern("Ana", 1600)
};
foreach (Employee e in staff)
{
Console.WriteLine(e.Name + ": " + e.MonthlyPay() + " €");
}
Schritt für Schritt, in Worten
  1. Das Array ist vom Typ Employee, aber die Objekte sind aus drei Klassen. In jedem Platz steht ein Pfeil.
  2. e zeigt auf Ivan, einen einfachen Employee: Es läuft MonthlyPay von Employee.
  3. e zeigt auf Maria, einen Manager: Es läuft ihre eigene Methode, Gehalt plus Bonus.
  4. e zeigt auf Ana, eine Praktikantin (Intern): Sie bekommt das halbe Gehalt.
  5. Dieselbe Zeile, drei verschiedene Methoden. Das Objekt entscheidet, nicht der Typ der Variable.

Das Array enthält drei Pfeile vom Typ Employee. Jeder zeigt auf ein Objekt einer anderen Klasse. In jeder Runde zeigt e auf das nächste Objekt, und der Aufruf geht an die Methode dieses Objekts: zuerst in Employee, dann in Manager, dann in Intern.

Die Wahl fällt, während das Programm läuft, nicht beim Kompilieren. Der Compiler weiß nicht, was im Array stehen wird. Er prüft nur, dass jeder Employee so eine Methode hat.

Probier es aus

Finde den Fehler

Das Programm ist dasselbe, aber die Praktikantin bekommt das volle Gehalt:

Ausgabe
Ivan: 2000 €
Maria: 3500 €
Ana: 1600 €

Hier ist die Klasse Intern. In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

Die Basisklasse ergänzen, nicht neu schreiben

Manchmal will die Unterklasse die Methode nicht ganz ersetzen, sondern nur etwas hinzufügen. Ein Manager wird wie ein Mitarbeiter beschrieben, plus der Bonus. Die Version der Basisklasse rufst du mit base.ToString() auf:

public override string ToString()
{
return base.ToString() + ", Bonus " + Bonus + " €";
}
Console.WriteLine(new Employee("Ivan", 2000));
Console.WriteLine(new Manager("Maria", 3000, 500));
Ausgabe
Ivan (2000 €)
Maria (3000 €), Bonus 500 €

Ändert Employee, wie es sich beschreibt, zieht der Manager von selbst mit.

Das ist auch das ToString() aus dem Modul „Klassen und Objekte“. Dort hast du schon eine Methode überschrieben: die Version in object, die virtual ist. Deshalb hast du override geschrieben.

Genauer, für Neugierige
  • base.Method() kannst du aus jeder Methode der Unterklasse aufrufen, nicht nur aus der überschreibenden.
  • Überschreiben ist nicht das Überladen aus dem Modul „Methoden“. Beim Überladen ist der Name gleich und die Parameter sind verschieden, und der Compiler wählt nach den Argumenten. Beim Überschreiben ist alles gleich, und das Objekt wählt, während das Programm läuft.

Probier es aus

Was fehlt?

Die Praktikantin wird wie ein Mitarbeiter beschrieben, plus das Wort „Praktikant“. Was steht in der Lücke?

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

    public override string ToString()
    {
        return .ToString() + ", Praktikant";
    }
}

Übung

Jeder auf seine Weise

Löse die Aufgaben in deinem Editor. Jede Aufgabe hat ein Array vom Typ der Basisklasse und eine Schleife.

1. Tierheim. Eine Klasse Animal mit einem Namen und einer Methode MakeSound(), die „irgendein Laut“ ausgibt. Dog und Cat überschreiben sie. Leg von allen dreien je eins in ein Array Animal[]:

Ausgabe
Gast: irgendein Laut
Rex: Wau!
Mieze: Miau, miau!

2. Die Studentin stellt sich vor. Student aus der letzten Lektion hat schon einen Notendurchschnitt. Überschreib Introduce(), sodass auch der Notendurchschnitt genannt wird. Benutze base.Introduce(); kopier die Zeile nicht aus Person.

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

3. Party. Eine Klasse Performance mit einem Titel und einer Methode Start(). DanceMove, BreakDanceMove (erbt von DanceMove, fügt eine Schwierigkeit hinzu) und Song überschreiben sie. Eine Klasse Party enthält ein Array Performance[] und hat eine Methode StartParty(), die alles der Reihe nach startet und am Ende sagt, dass die Party begonnen hat.

Ausgabe
Wir tanzen moonwalk
Wir tanzen windmill
Schwierigkeit 8 von 10
Wir singen 'Bohemian Rhapsody'
Es beginnt: Tombola
Die Party hat begonnen!

Weitere Aufgaben

  • Honorarkraft. Füg den Mitarbeitern einen Contractor hinzu, mit Stunden und einem Stundensatz. Sein Monatsgehalt ist Stunden × Satz. Berechne die Lohnsumme für Ivan, Maria, Ana und Peter (60 Stunden zu 25 €):
Ausgabe
Lohnsumme: 7800 €
  • Lohnzettel. Gib Employee eine Methode Payslip(), die den Text „Name: Gehalt €“ mit dem Monatsgehalt zurückgibt. Schreib sie nur in Employee. Warum funktioniert sie auch für den Manager und die Praktikantin richtig?

Test

Teste dich

1. Employee e = new Manager("Maria", 3000, 500); Welches MonthlyPay läuft bei e.MonthlyPay()?
2. Employee e = new Manager("Maria", 3000, 500); Kommst du an den Bonus von e?
3. Was musst du in die Basisklasse schreiben, damit die Unterklasse die Methode überschreiben kann?
4. Was macht base.toString() im toString() der Unterklasse?
5. Was ist der Unterschied zwischen Überschreiben und Überladen?

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