Kapitel 9 · Übung
Vererbung und Polymorphie
Verschiedene Objekte, derselbe Vorgang
Bisher war jede Frage in diesem Kurs von derselben Art: ein Fragetext, dessen Antwort mit einem gespeicherten Text verglichen wird. Echte Quizze sind vielfältiger.
Eine Textfrage vergleicht die Eingabe.
Eine Auswahlfrage akzeptiert eine von mehreren Optionen anhand ihres Buchstabens.
Eine Zahlenfrage akzeptiert alles, was nah genug an der richtigen Zahl liegt.
Drei unterschiedliche Regeln zur Richtigkeitsprüfung. Interessant ist, was der Rest des Programms darüber wissen muss.
Die Zahlenbeispiele verwenden eine kleine eingebaute Hilfsfunktion. abs(number) gibt den Abstand der Zahl von null zurück, daher ist abs(-0.05) gleich 0.05. Wenn wir diesen Abstand mit einer Toleranz vergleichen, erfahren wir, ob eine numerische Antwort nah genug liegt.
Die Version, die alles weiß
Hier ist der naheliegende erste Versuch und der Grund für dieses Kapitel:
Es funktioniert. Überlege nun, was als Nächstes passiert.
Eine vierte Art kommt hinzu, etwa Wahr-oder-falsch-Fragen. Du ergänzt hier einen Zweig. Dann muss die Punktewertung die Art kennen und erhält dieselbe Kette. Dann die Anzeige, der Bericht und schließlich der Code, der Fragen aus der Speicherung liest.
Der String kind hat sich im Programm verteilt. Jede Stelle, die ihn liest, muss gemeinsam mit den anderen gefunden und aktualisiert werden. Übersiehst du eine, verhält sich die neue Frageart unbemerkt wie der letzte Zweig oder fällt bis zu return False durch und ist für alle falsch.
Das Anzeichen
Erkenne dieses Muster: eine Kette von Bedingungen, die fragt, was für eine Art von Ding das ist, gefolgt von Code für den jeweiligen Fall.
if kind == "text":
...
elif kind == "choice":
...
elif kind == "numeric":
...
Eine solche Kette ist in Ordnung. Das Problem ist, dass sie sich vermehren. Kopien derselben Kette in verschiedenen Funktionen entwickeln sich auseinander. Einen Typ hinzuzufügen wird zur archäologischen Expedition.
Vergleiche das mit einer Kette, die diesem Muster nicht entspricht:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
Sie fragt nach einem Wert, nicht nach der Art des Objekts. Eine zusätzliche Notenstufe ändert nur eine Stelle. Lass diese Kette in Ruhe. Entfernen solltest du die wiederholte Verzweigung nach der Art eines Objekts. Das ist eine andere Frage als die Identitätsfrage aus Kapitel 7, ob zwei Namen auf dasselbe Objekt verweisen.
Welche Kette ist das Warnsignal, um das sich dieses Kapitel kümmert?
Der nächste Schritt
Der Ausweg ist die Idee, die Kapitel 4 eingeführt hat und dieses Kapitel vervollständigt: Lege das Verhalten in das Objekt.
Eine Kante bleibt bewusst noch ungeschliffen: NumericQuestion.is_correct("three") löst ValueError aus. Alle drei Klassen bieten denselben Methodennamen, halten aber noch nicht dieselbe Zusage für gewöhnliche Eingaben ein. Lektion 8 greift diese Verhaltenszusage auf und repariert sie.
Die Schleife am Ende ist der entscheidende Punkt. Sie ruft dreimal question.is_correct(response) auf, erhält drei verschiedene Regeln und enthält kein einziges if. Sie fragt nie, welche Art von Frage sie hat.
Jedes Objekt weiß, was es ist, also muss es nichts anderes wissen. Ein vierter Typ bedeutet eine neue Klasse, die dieselben Vorgänge einhält. Verwendender Code wie diese Schleife funktioniert unverändert weiter.
Mehrere unterschiedliche Typen über einen gemeinsamen Vorgang zu verwenden, ohne dass der aufrufende Code sie unterscheiden muss, heißt Polymorphie. Der Name ist länger als die Idee.
Worum es in diesem Kapitel geht
Vielleicht ist dir aufgefallen, dass diese drei Klassen ohne die Mechanik geschrieben wurden, mit der Vererbung oft eingeführt wird. Es gibt keine Basisklasse, und nichts wird geteilt.
Das ist Absicht. Polymorphie ist das Ziel, Vererbung ein Weg dorthin. Sie ist in weniger Situationen nützlich, als viele Einführungen vermuten lassen. Die nächsten beiden Lektionen zeigen, wie weit du ganz ohne Vererbung kommst. Wenn sie in Lektion 4 auftaucht, kannst du deshalb beurteilen, ob sie ihren Platz verdient.
Aufgabe
Dieses Programm entscheidet alles anhand der Frageart, und zwar an zwei Stellen, die schon auseinanderlaufen: score_quiz behandelt drei Arten, describe_all nur zwei.
Die drei validierten Frageklassen sind vorgegeben. Ersetze beide Dictionary-basierten Bedingungsketten durch polymorphe Aufrufe.
Schreibe anschließend score_quiz(questions, responses) und describe_all(questions) so um, dass keine Funktion ein if zur Frageart enthält. Sie sollen bei jeder Frage dieselben zwei Methoden aufrufen und jedes Objekt seine eigene Regel liefern lassen.
Die Punktwerte bleiben unverändert. Konzentriere dich auf die eine neue Schwierigkeit: verwendender Code, der jedes Objekt ohne Typprüfung um denselben Vorgang bittet.