Kapitel 6 · Übung
Zusammenarbeit zwischen Objekten
Ein Objekt an ein anderes übergeben
Eine Zeile aus dem letzten Kapitel verdient einen zweiten Blick:
attempt.record(question, response)
Ein Attempt erhält ein vollständiges Question-Objekt. Wir hätten die Methode auch so schreiben können, dass sie stattdessen dessen Bestandteile erhält:
attempt.record(question.prompt, question.answer, question.points, response)
Beides funktioniert. Der Unterschied ist das Thema dieses Kapitels. Er entscheidet darüber, wie viel deines Programms sich ändern muss, wenn sich eine Kleinigkeit ändert.
Ein Objekt an der Tür zerlegen
Hier ist eine Funktion report_line, die auf die zweite Weise geschrieben wurde:
Lies den Aufruf. Jede aufrufende Stelle muss wissen, dass eine Frage genau diese drei Teile in dieser Reihenfolge hat, und sie von Hand herausziehen. Betrachte dann den Funktionskörper: correct = response == answer bildet die Regel nach, für die bereits Question.is_correct verantwortlich ist.
Angenommen, Fragen erhalten eine Option case_sensitive. Die Signatur dieser Funktion ändert sich, jede Aufrufstelle ändert sich, und die Vergleichsregel muss hier ebenso wie in Question angepasst werden. Die Funktion hat nie um diese Aufgabe gebeten.
Das Ganze übergeben
Dieselbe Ausgabe. Der Aufruf lautet jetzt report_line(question, "3") und liest sich so, wie er gemeint ist. Die Vergleichsentscheidung wird erfragt statt nachgebaut. Ergänze nun case_sensitive in Question, und diese Funktion braucht keine Änderung, denn sie wusste nie, wie die Richtigkeit entschieden wird.
Eine Frage erhält ein neues Attribut, und is_correct beginnt, es zu verwenden. Welche Version von report_line muss sich ändern?
Auf dieses Anzeichen achten
Das Problem der ersten Version lässt sich klar benennen: eine lange Parameterliste, deren Argumente alle aus demselben Objekt stammen, gefolgt von Code, der eine Entscheidung nachbaut, die dieses Objekt selbst treffen könnte.
Du erkennst es schon an der Aufrufstelle, ohne die Funktion zu lesen:
summarize(attempt.learner, attempt.score, attempt.response_count)
Drei Argumente, eine Quelle. Dieser Aufruf fragt eigentlich nach einem Attempt.
Wann die Einzelteile richtig sind
Mache daraus keine Regel, nach der Funktionen immer Objekte erhalten müssen. Eine Funktion, die tatsächlich mit einem einzelnen Wert arbeitet, sollte diesen Wert erhalten:
def shorten(text, limit):
...
shorten hat keinen Grund, ein Question-Objekt zu erhalten. Es arbeitet mit Text. Übergäbst du ihm eine Frage, wäre es für jeden anderen String im Programm unbrauchbar. Frage dich, von welchen Werten die Aufgabe der Funktion tatsächlich abhängt. Stammen sie alle aus einem Objekt und braucht die Funktion Entscheidungen, die dieses Objekt selbst treffen könnte, übergib das Objekt.
Die Übung enthält eine Funktion der ersten Form. Gib ihr die zweite.
Aufgabe
Beide Funktionen zerlegen ein Objekt schon an der Tür und bauen dann Entscheidungen nach, die es selbst treffen könnte.
Schreibe describe_result so um, dass es (question, response) entgegennimmt, und summarize_attempt so, dass es (attempt) entgegennimmt. Frage jedes Objekt nach dem, was du brauchst, statt seine Einzelteile zu erhalten. Verwende seine vorhandenen Methoden, statt deren Regeln zu wiederholen.
Passe die beiden Aufrufe am Ende an. Die Ausgabe darf sich nicht ändern: Die Übung verbessert die Schnittstelle, während das Verhalten gleich bleibt.