Vererbung und Polymorphie · Übung
Verhalten überschreiben und erweitern
Jede Unterklasse der letzten Lektion lieferte ihr eigenes is_correct. Das heißt Überschreiben: Eine Unterklasse definiert eine Methode, die die Basis bereits hat, und die Version der Unterklasse wird ausgeführt.
Eine Unterklasse kann noch etwas Zweites tun. Der Unterschied ist wichtig.
Ersetzen oder ergänzen
Ein überschriebenes describe ersetzt die Basisversion vollständig:
Die Punktzahl ist aus der Beschreibung verschwunden. Die Unterklasse hat den ganzen Satz neu geschrieben. Jede künftige Verbesserung der Basisversion geht an ihr vorbei.
Oft möchtest du die Basisversion plus eine Ergänzung:
super().describe() ruft innerhalb der überschreibenden Methode die Basisversion auf, und die Unterklasse ergänzt das Ergebnis. Die Punkte erscheinen wieder und bleiben auch dann erhalten, wenn die Basis ihre Formulierung ändert.
Es ist dasselbe super() wie in __init__, mit derselben Aufgabe: die Basisversion dieser Methode ausführen. __init__ ist lediglich der Fall, dem du zuerst begegnest.
Das describe einer Unterklasse ruft super().describe() auf und ergänzt das Ergebnis. Was gewinnt es gegenüber einem vollständig neu geschriebenen String?
Woher die einzelnen Methoden kommen
Bei einer Klassenfamilie brauchst du eine verlässliche Antwort auf „Welche Version läuft?“ Python sucht zuerst in der eigenen Klasse des Objekts, dann in ihrer Basis und dann in deren Basis. Beim ersten Treffer hört es auf.
summary ist nur in Question definiert und ruft self.describe() auf. Ausgeführt wird die Version von TextQuestion, denn self ist ein TextQuestion, und dort beginnt die Suche.
Das verdient einen Moment Aufmerksamkeit. Es macht eine Basisklasse über das Teilen von Code hinaus nützlich. Question.summary wurde geschrieben, ohne zu wissen, welche Fragearten jemals existieren würden. Trotzdem ruft es für jede das richtige describe auf. Eine Basisklasse kann die Form eines Vorgangs festlegen und den veränderlichen Teil ihren Unterklassen überlassen.
Verantwortungsbewusst überschreiben
Zwei Regeln verhindern, dass eine Überschreibung den aufrufenden Code überrascht.
Akzeptiere, was die Basis akzeptiert. Nimmt Question.is_correct(response) ein Argument entgegen, muss auch die Unterklassenversion mit einem Argument funktionieren. Eine Unterklasse, die ein weiteres verlangt, beschädigt jeden für die Basis geschriebenen Aufruf.
Gib zurück, was die Basis zurückgibt. Gibt is_correct entweder True oder False zurück, wird eine Unterklasse mit "yes", einer Punktzahl oder None für „unbeantwortet“ in if question.is_correct(...) auf unerwartete Weise behandelt.
Kein Fehler. Nur Summary: None, das irgendwo auf einem Bildschirm landet. Die übernächste Lektion widmet sich vollständig dieser Art von Problem.
Zustand und Verhalten erweitern
__init__ folgt demselben Muster. Jetzt erkennst du es als einen Fall einer allgemeinen Regel:
def __init__(self, prompt, points, tolerance):
super().__init__(prompt, points) # what every question needs
self.tolerance = tolerance # what this kind adds
Rufe zuerst die Basis auf, ergänze danach. Eigenen Zustand vor super().__init__ aufzubauen ist zulässig und manchmal nötig. Es bedeutet aber, dass ein Teil des Objekts schon existiert, bevor die Basis fertig ist. Damit kehrt das Problem des halb aufgebauten Objekts aus Kapitel 5 über einen neuen Weg zurück.
Aufgabe
Jede Unterklasse schreibt hier die gesamte Beschreibung neu. Eine Änderung der Basisformulierung würde daher keine erreichen.
Lass alle drei die Basisbeschreibung erweitern statt ersetzen. Jedes describe() soll super().describe() aufrufen und seinen eigenen Teil ergänzen:
TextQuestion:"<base> Type your answer."ChoiceQuestion:"<base> Options: A, B, C."mit seinen eigenen OptionenNumericQuestion:"<base> Answer within 0.2."mit seiner eigenen Toleranz
Die Basisformulierung lautet "<prompt> (<points> points)". Keine Unterklasse darf dieses Format enthalten. Die Bewertung ändert die Basisformulierung und prüft, ob alle drei folgen. Genau deshalb verwenden wir diesen Weg.
Ergänze anschließend nur in der Basis summary(), das "Summary: <description>" zurückgibt. Einmal geschrieben muss es für jede Art die richtige Beschreibung erzeugen.