0%

De geldige toestand van objecten bewaken · oefening

Publieke en interne attributen

Attempt.record beheert nu de regel die een ingevoerd antwoord in punten omzet. De tegenstrijdigheid uit de vorige les is echter nog steeds mogelijk:

attempt.score = 500

Niets aan de klasse zegt dat score haar eigen zaak is. Elk attribuut ziet er even publiek uit, omdat elk attribuut even publiek is.

Een markering die zegt “dit is van mij”

De conventie van Python is één underscore aan het begin:

Try it

learner heeft geen underscore: het hoort bij wat een Attempt aan de buitenwereld biedt. _responses, _score en _finished hebben die wel: ze zijn de manier waarop deze klasse dingen bijhoudt en de klasse behoudt het recht om ze te veranderen.

Wees eerlijk over wat dit doet

De laatste regel van dat voorbeeld drukte 2 af. Lees die nog eens.

attempt._score werkte. De underscore maakte geen afgedwongen toegangsbeperking voor dit gewone attribuut. Niets werd geblokkeerd en niets werd gemeld.

Try it

Nog steeds toegestaan. Iedereen kan dit nog doen en de taal maakt geen bezwaar.

Wat bereikte de underscore dan? Die veranderde de betekenis van die regel voor een menselijke lezer. Eerder leek attempt.score = 500 op gebruik van de klasse. Nu lijkt attempt._score = 500 op het omzeilen ervan. Dat valt een reviewer op. De schrijver van Attempt heeft gezegd: “Dit hoort niet bij de ondersteunde interface en ik kan het morgen veranderen.”

Dat is nuttig, maar zwakker dan een garantie. Behandel de underscore als een bordje op een deur, niet als een slot. Python heeft ook mogelijkheden zoals naamaanpassing met dubbele underscores, maar één underscore aan het begin is een conventie en geen toegangsmodifier.

Wat gebeurt er wanneer code buiten de klasse attempt._score = 500 uitvoert?

Wat publiek blijft

Dingen als intern markeren is maar het halve werk. Als _score intern is, heeft de buitenwereld nog steeds een goede manier nodig om te vragen hoe de poging verloopt, anders is de klasse nutteloos.

Dat betekent bewust bepalen wat de klasse belooft:

  • learner, leesbaar, omdat die de identiteit van de poging vormt;

  • record(question, response), omdat het object juist bestaat om die gebeurtenis te verwerken;

  • iets dat de score meldt, omdat het melden nooit het probleem was. Toewijzen was dat wel.

Het onderscheid om mee te nemen is dat tussen de interface van een object (wat het aanroepers belooft) en de representatie (hoe het dingen op dit moment opslaat). _score is representatie. Een methode die de score teruggeeft, is interface. Door ze apart te houden, kun je de tweede veranderen zonder elk programma te breken dat de eerste gebruikt. Dat doet de volgende les precies.

Voorlopig is een gewone methode genoeg:

def current_score(self):
    return self._score

Niet elegant, en je hebt gelijk als je attempt.current_score() een omslachtige manier vindt om een getal op te vragen dat eerder gewoon attempt.score was. Les 5 lost die onhandigheid op zonder de bescherming op te geven.

Opdracht

Bepaal welke delen van Attempt de interface vormen en welke de representatie, en markeer ze vervolgens.

Hernoem responses en score zodat hun namen met één underscore beginnen en werk elk gebruik binnen de klasse bij. Laat learner publiek: aanroepers mogen daarbij. De levenscyclustoestand _finished en de methode finish() uit de vorige les zijn al meegenomen.

Voeg vervolgens de twee publieke methoden toe die de klasse nodig heeft om bruikbaar te blijven: current_score(), die de score teruggeeft, en response_count(), die teruggeeft hoeveel antwoorden zijn geregistreerd. Geen van beide mag een aanroeper iets laten veranderen.

Voer het programma uit. De uitvoer moet hetzelfde zijn als vóór het hernoemen. Dat is het punt: de interface bleef gelijk terwijl de representatie veranderde.