Zusammenarbeit zwischen Objekten · Übung
Fachlogik von Ein- und Ausgabe trennen
Alle Klassen, die du in diesem Kapitel geschrieben hast, geben Werte zurück. Keine verwendet eine Bildschirmausgabe. Das war Absicht. Bevor die erste Klasse, die scheinbar eine Ausgabe braucht, diese Gewohnheit durchbricht, sollten wir den Grund klären.
Stell dir vor, QuizAttempt.answer wäre besonders hilfsbereit geschrieben worden:
Das funktioniert und ist für ein kleines Skript bequem. Der Preis zeigt sich später, auf einmal.
Was die Ausgabe kostet
Frage dich, was passiert, wenn das Programm wächst:
Tests. Den Punktestand kannst du direkt prüfen. Für die Rückmeldung musst du dagegen die Standardausgabe abfangen.
Eine andere Oberfläche. Soll das Quiz auf eine Webseite, muss die Meldung in HTML erscheinen. Die Methode schreibt in ein Terminal, das es dort nicht mehr gibt.
Sprache. Die Rückmeldung zu übersetzen bedeutet, den Code für die Punktewertung zu bearbeiten.
Wiederverwendung. Gespeicherte Versuche für eine Zusammenfassung auszuwerten gibt nun auch ungefragte Rückmeldungen aus.
Stille. Es gibt keine Möglichkeit, Punkte ohne Ausgabe zu berechnen.
Es ist jedes Mal dasselbe Problem: Eine Entscheidung über die Darstellung steht in einem Objekt, dessen Aufgabe die Regeln sind.
Die beiden Bereiche trennen
Der Bereich der Regeln heißt Domäne oder Fachbereich: Fragen, Antworten, Punktestände, Bestehen. Diese Dinge existieren auch, wenn niemand hinsieht. Darum liegt die Benutzerschnittstelle: Ausgabe, Eingaben, Formatierung, Farben, HTML.
Lass Domänenobjekte still arbeiten. Sie sollen berechnen, entscheiden und zurückgeben. Die Ausgabe erfolgt am Rand des Programms:
Dieselbe Ausgabe. Jetzt gibt answer eine Tatsache zurück, feedback macht daraus einen Satz, und print bringt den Satz auf den Bildschirm. Drei Aufgaben, drei Stellen. Nur die letzte interessiert sich dafür, dass ein Terminal existiert.
Was davon gehört in eine Domänenklasse wie QuizAttempt?
Welche Form das dem Programm gibt
Nach der Häufigkeit ihrer Änderungen geordnet hat ein so aufgebautes Programm Schichten:
entry point input(), print(), the loop that drives everything
formatting ScoreReport, feedback(): facts into text
domain Quiz, Question, QuizAttempt, Response: the rules
Abhängigkeiten zeigen nach unten. Die Formatierung kennt die Domäne, die Domäne weiß nichts über die Formatierung. Deshalb kann dasselbe Quiz einem Terminalprogramm, einer Webseite und einer Testsammlung dienen, ohne den Unterschied zu bemerken.
Ein schneller Test für deinen Code: Würden die Domänenklassen noch Sinn ergeben und sich weiterhin testen lassen, wenn du jedes print und input aus dem Programm löschtest? Falls ja, liegt die Grenze richtig.
Mit Augenmaß vorgehen
Das verbietet nicht jede Ausgabe. Ein zehnzeiliges Skript, das während seiner Arbeit ausgibt, ist in Ordnung. Ihm drei Schichten hinzuzufügen wäre schlechter.
Es bedeutet auch nicht, dass Domänencode niemals etwas melden darf. Wie in Kapitel 5 ValueError auszulösen meldet ein Problem, ohne über dessen Darstellung zu entscheiden. Der aufrufende Code fängt die Ausnahme ab und wählt zwischen rotem Hinweis, Logzeile oder erneutem Versuch. Kapitel 12 baut genau auf dieser Trennung auf.
Die Übung nimmt ein funktionierendes Quizprogramm mit Ausgaben in mehreren Klassen und zieht die Darstellung an den Rand des Programms.
Aufgabe
Der Startcode vermischt Rückmeldungen zu Antworten mit der Punktewertung. Verschiebe die Ausgaben aus QuizAttempt, damit die Domänenklassen stattdessen Tatsachen zurückgeben:
QuizAttempt.answer(question, given)gibt zurück, ob die Antwort richtig war.QuizAttempt.scoreundcorrect_countbleiben Properties, undresponses()gibt eine Kopie der Liste seiner festgehaltenen Antwortobjekte zurück.ScoreReport.review_lines()gibt eine Liste von Strings zurück: eine ÜberschriftReview of <quiz title>, dann eine Zeile pro Antwort in der erfassten Reihenfolge. Jede Antwortzeile beginnt mit zwei Leerzeichen und lautet<prompt> -> <given> (right)oder<prompt> -> <given> (wrong).
Gib in run_quiz nach jeder Antwort das Ergebnis der vorgegebenen Funktion feedback(question, correct) aus, dann die Rückblickzeilen und schließlich die bestehende Zusammenfassung des Punktestands. Nur run_quiz verwendet Ausgaben. Die Erzeugung von Fragen, die Punktewertung und das Lesen von Domänendaten dürfen keine Ausgabe erzeugen.
Das vorgegebene Programm sollte Folgendes ausgeben:
Correct!
Not quite. The answer was def.
Review of Chapter 5 Review
What does len('cat') return? -> 3 (right)
Which keyword starts a function? -> class (wrong)
Mina: 2 of 5