Zusammenarbeit zwischen Objekten · Übung
Hat-ein-Beziehungen aufbauen
Das Quiz, das du geschrieben hast, hält Question-Objekte. Das Attempt aus Kapitel 5 hält Ergebnisse. Solange es jeweils nur eine solche Anordnung gab, brauchte sie keinen Namen. Ein Programm mit fünf Klassen braucht jedoch eine Möglichkeit, ihre Zusammenhänge zu beschreiben.
In beiden Fällen ist es dieselbe Beziehung: hat ein. Ein Quiz hat Fragen. Ein Versuch hat Ergebnisse.
Einen Entwurf laut lesen
Beschreibe die Bestandteile in einfachen Sätzen und verwende dabei nur „hat ein“ und „ist ein“:
a Quiz has many Questions
a QuizAttempt has a Quiz
a QuizAttempt has many Responses
a Response has a Question
Das ist ein Entwurf. Er passt auf vier Zeilen, lässt sich ohne Python-Kenntnisse lesen und legt etwas fest: Jeder Pfeil zeigt, welches Objekt eine Referenz auf welches andere hält und somit welches Objekt das andere um etwas bitten kann.
Objekte auf diese Weise aus anderen Objekten aufzubauen heißt Komposition. Sie ist der übliche Ausgangspunkt für die Struktur eines Programms und bleibt es im Rest dieses Kurses. Kapitel 9 führt „ist ein“-Beziehungen ein. Diese sind in weniger Situationen nützlich, als viele Einführungen vermuten lassen.
Den Entwurf aufschreiben
Response fragt die Frage bei Eingang der Antwort und hält anschließend diese Entscheidung und die zu diesem Zeitpunkt verfügbaren Punkte fest. Kapitel 5 verwendete dieselbe Regel für Momentaufnahmen: Eine spätere Änderung der Frage darf eine bereits abgegebene Antwort nicht umwerten.
Vier Klassen, denen die vier Zeilen des Entwurfs oben genau entsprechen. QuizAttempt.__init__ nimmt ein Quiz entgegen und behält es. Response.__init__ nimmt ein Question entgegen und behält es. Jedes Objekt hält die Dinge, die es hat.
Eine Referenz, keine Kopie
attempt.quiz und die Variable quiz verweisen auf dasselbe Objekt. Nichts wurde kopiert:
Eine über quiz hinzugefügte Frage verändert das, was attempt.quiz meldet, denn es gibt nur ein Quiz. Hier begegnet dir die Idee „zwei Namen für ein Objekt“ aus Kapitel 4 wieder, nun über zwei Klassen hinweg.
Diese Version folgt bewusst dem aktuellen Quiz. Eine neu hinzugefügte Frage zählt deshalb ebenfalls als noch offen. Das Projekt trifft für historische Berichte eine andere Entscheidung und hält die Quizdaten vom Beginn fest. Genau deshalb war die vorige Lektion beim Herausgeben interner Listen vorsichtig: Gemeinsame Referenzen wirken in beide Richtungen.
Ein QuizAttempt hält ein Quiz. Was bedeutet das im Speicher?
Was die einzelnen Klassen nicht mehr wissen müssen
Sieh dir an, was QuizAttempt.score tut. Es summiert response.earned und ist fertig.
Es weiß nicht, dass eine Antwort nur dann Punkte bringt, wenn sie richtig ist. Es weiß nicht, dass diese Punkte von einer Frage stammen. Es weiß nicht, wie eine Frage Richtigkeit entscheidet. Jede dieser Regeln lebt in genau einer Klasse, und die Klasse darüber fragt einfach nach.
Vergleiche das mit einer einzigen großen Klasse: ein Quiz mit parallelen Listen für Fragetexte, erwartete Antworten, Punkte, eingegebene Antworten und verdiente Punkte. Jede Methode müsste mehrere Listen gleichzeitig durchlaufen und ihre Längen aufeinander abstimmen. Von diesem Entwurf ging Kapitel 1 aus. Jede „hat ein“-Zeile oben ist eine Beziehung, die früher durch einen übereinstimmenden Index dargestellt wurde.
Die Grenze ziehen
Wenn du unsicher bist, ob eine Klasse ein anderes Objekt oder nur dessen Daten halten sollte, hilft diese Frage: Hat das kleinere Ding eigenes Verhalten?
Response hält ein Question-Objekt statt eines Fragetext-Strings, weil es is_correct fragen muss. Müsste eine Antwort nur den Fragetext anzeigen, wäre es vernünftig und einfacher, nur diesen Text zu speichern.
Die Übung baut die Klassen Response und QuizAttempt auf dem Quiz auf, das du bereits hast.
Aufgabe
Vervollständige die Klasse oberhalb des vorgegebenen Response entsprechend diesem Entwurf:
a Response has a Question
a QuizAttempt has a Quiz, and has many Responses
Response ist vorgegeben. Es fragt sein Frageobjekt bei Eingang einer Antwort einmal und hält dann Richtigkeit und verdiente Punkte als Momentaufnahmen fest. Eine spätere Bearbeitung der Frage kann dadurch die Vergangenheit nicht umschreiben.
Baue QuizAttempt: Es hält sein Quiz und den Namen der lernenden Person. answer(question, given) erfasst ein Response-Objekt. score summiert die verdienten Punkte der Antworten, correct_count zählt die richtigen, und remaining meldet, wie viele Quizfragen noch unbeantwortet sind. In diesem kleinen Modell beantwortet der aufrufende Code jede Quizfrage höchstens einmal. Die Übung verlangt nicht, wiederholte oder nicht zum Quiz gehörende Fragen abzulehnen.
Lass jede Klasse die Regeln der anderen nicht kennen. QuizAttempt sollte niemals eine Eingabe mit einer erwarteten Antwort vergleichen, und Response sollte niemals ins Quiz hineinsehen.