0%

Gültigen Objektzustand schützen · Übung

Öffentliche und interne Attribute

Attempt.record ist jetzt für die Regel verantwortlich, die eine Antwort in Punkte umsetzt. Der Widerspruch aus der vorherigen Lektion bleibt allerdings möglich:

attempt.score = 500

Nichts an der Klasse sagt, dass score ihre eigene Angelegenheit ist. Alle Attribute sehen gleichermaßen öffentlich aus, weil sie es auch sind.

Ein Zeichen für „Das gehört mir“

Die Python-Konvention ist ein einzelner führender Unterstrich:

Try it

learner hat keinen Unterstrich: Es gehört zu dem, was ein Attempt nach außen anbietet. _responses, _score und _finished haben einen: Sie bilden die aktuelle interne Speicherung ab, und die Klasse behält sich vor, sie zu ändern.

Genau hinsehen, was das bewirkt

Die letzte Zeile dieses Beispiels hat 2 ausgegeben. Lies sie noch einmal.

attempt._score hat funktioniert. Der Unterstrich hat für dieses gewöhnliche Attribut keine durchgesetzte Zugriffsbeschränkung geschaffen. Nichts wurde blockiert oder gemeldet.

Try it

Weiterhin erlaubt. Jeder Code kann das noch tun, und die Sprache hat nichts dagegen.

Was hat der Unterstrich dann bewirkt? Er hat die Bedeutung der Zeile für Menschen verändert. Vorher sah attempt.score = 500 nach normaler Verwendung der Klasse aus. Jetzt sieht attempt._score = 500 nach einem Eingriff an ihr vorbei aus. Beim Review fällt das auf. Wer Attempt geschrieben hat, sagt damit: „Das gehört nicht zur unterstützten Schnittstelle, und ich könnte es morgen ändern.“

Das ist nützlich, aber keine Garantie. Betrachte den Unterstrich als Schild an einer Tür, nicht als Schloss. Python bietet auch Funktionen wie die Namensumformung bei doppelten Unterstrichen, doch ein einzelner führender Unterstrich ist eine Konvention, kein Zugriffsmodifikator.

Was passiert, wenn Code außerhalb der Klasse attempt._score = 500 ausführt?

Was öffentlich bleibt

Dinge als intern zu kennzeichnen ist nur die halbe Aufgabe. Wenn _score intern ist, braucht der äußere Code weiterhin einen vorgesehenen Weg, nach dem Stand des Versuchs zu fragen. Sonst ist die Klasse nutzlos.

Dafür musst du bewusst entscheiden, was die Klasse zusichert:

  • learner ist lesbar, denn dieser Name gehört zur Identität des Versuchs.

  • record(question, response) steht zur Verfügung, denn dieses Ereignis soll das Objekt verarbeiten.

  • Etwas meldet den Punktestand, denn ihn zu melden war nie das Problem. Ihn zuzuweisen war es.

Merke dir die Unterscheidung zwischen der Schnittstelle eines Objekts, also seinen Zusagen an aufrufenden Code, und seiner Repräsentation, also seiner aktuellen Art der Speicherung. _score ist Repräsentation. Eine Methode, die den Punktestand zurückgibt, ist Schnittstelle. Wenn du beides trennst, kannst du die Repräsentation ändern, ohne jedes Programm zu beschädigen, das die Schnittstelle verwendet. Genau das zeigt die nächste Lektion.

Vorerst reicht eine gewöhnliche Methode:

def current_score(self):
    return self._score

Elegant ist das nicht. Du findest attempt.current_score() zu Recht umständlich für eine Zahl, die früher einfach attempt.score hieß. Lektion 5 beseitigt diese Umständlichkeit, ohne den Schutz aufzugeben.

Aufgabe

Entscheide, welche Teile von Attempt zur Schnittstelle und welche zur Repräsentation gehören, und kennzeichne sie.

Benenne responses und score so um, dass ihre Namen mit einem einzelnen Unterstrich beginnen, und passe jede Verwendung innerhalb der Klasse an. Lass learner öffentlich: Darauf darf aufrufender Code zugreifen. Der Lebenszykluszustand _finished und die Methode finish() aus der vorherigen Lektion sind bereits übernommen.

Ergänze dann die beiden öffentlichen Methoden, mit denen die Klasse nützlich bleibt: current_score() gibt den Punktestand zurück, response_count() die Anzahl der erfassten Antworten. Keine der beiden soll aufrufendem Code Änderungen ermöglichen.

Führe das Programm aus. Die Ausgabe sollte dieselbe sein wie vor der Umbenennung. Genau darum geht es: Die Schnittstelle blieb erhalten, während sich die Repräsentation änderte.