0%

De geldige toestand van objecten bewaken · oefening

Verander de toestand via betekenisvolle methoden

Een Question is nu veilig aan te maken. Aanmaken is echter pas de eerste grens: de attributen blijven daarna beschrijfbaar.

De meeste interessante objecten veranderen wel. Een quizpoging krijgt antwoorden. Een score gaat omhoog. Deze les beantwoordt niet of een object mag veranderen, maar hoe om die verandering moet worden gevraagd.

Een tweede soort waarde

Hier is een Attempt: één cursist die een quiz doorloopt, op de voor de hand liggende manier gebouwd.

Try it

Stel nu dat een cursist een vraag van 2 punten goed beantwoordt. Het programma dat deze klasse gebruikt, moet de wijziging zelf uitvoeren:

Try it

Dat werkt. Het betekent ook dat elk deel van het programma dat een antwoord registreert beide regels moet onthouden, in de juiste volgorde en met de juiste puntenwaarde. Als de tweede regel ontbreekt, klopt de score ongemerkt niet meer met de antwoorden.

Erger nog, niets voorkomt dit:

Try it

Eén geregistreerd antwoord en een score van 500. Het object is intern tegenstrijdig en dat ontstond via een gewone toewijzing die er op de plek waar ze staat volkomen redelijk uitziet.

Geef de verandering een naam

De oplossing is niet verandering verbieden. Het is het object de verandering zelf laten uitvoeren via een methode waarvan de naam zegt wat er gebeurde:

Try it

Twee antwoorden, waarvan één goed, en een score van 2. De aanroeper weet niet meer hoe een score wordt berekend en hoeft dat ook niet te weten. Die zegt wat er in de wereld gebeurde en het object bepaalt wat dat voor zijn eigen toestand betekent.

Let op wat er is verplaatst. De regel “een goed antwoord is de punten van de vraag waard” stond eerst in de aanroepende code, herhaald op elke plek die een antwoord registreerde. Nu bestaat die één keer, binnen het object dat de score beheert.

Waarom is attempt.record(question, response) een betere manier om toestand te veranderen dan rechtstreeks aan attempt.score toe te wijzen?

Noem methoden naar gebeurtenissen, niet naar toewijzingen

Een nuttige test bij het benoemen van een methode: beschrijft de naam iets dat gebeurde of alleen de toewijzing?

ZwakkerSterker
set_score(value)record(question, response)
set_responses(items)record(question, response)
update_state(a, b)finish()

Een methode met de naam set_score is een vermomde toewijzing. Ze accepteert elk getal dat de aanroeper bedenkt, precies de situatie waarmee we begonnen. record ontvangt iets dat echt is gebeurd en leidt zelf de gevolgen af.

Een gebeurtenis kan ook een levenscyclus afsluiten. finish() registreert dat een poging geen antwoorden meer accepteert. Zodra die gebeurtenis heeft plaatsgevonden, keert record() terug zonder de antwoorden of score te veranderen. De bewakingsvoorwaarde hoort aan het begin van record(), omdat de betekenis van juist die bewerking is veranderd:

def record(self, question, response):
    if self.finished:
        return

    # Record the response and update the score together.

Dit is nog steeds dezelfde ontwerpstap: aanroepers melden een gebeurtenis en het object beslist welke toestandswijzigingen op dat moment in zijn leven geldig zijn.

Dit is geen regel dat een klasse nooit een eenvoudige setter mag bieden. Les 6 schrijft er bewust één, met controles. Het herinnert je eraan dat de eerste ontwerpvraag is welke gebeurtenissen een object meemaakt, niet welke attributen je wilt aanpassen.

De score is in deze versie nog steeds rechtstreeks toewijsbaar, dus de eerdere tegenstrijdigheid blijft mogelijk. De volgende les markeert die opslag als intern; properties regelen daarna de toegang via de publieke interface.

Opdracht

Geef Attempt een methode record, zodat het aanroepende programma nooit zelf aan responses of score komt.

record(question, response) moet het antwoord opslaan en, als het antwoord juist is, de punten van die vraag bij de score optellen. Een onjuist antwoord wordt wel geregistreerd, maar levert niets op.

Maak daarna finish() af, waarmee de poging als voltooid wordt gemarkeerd. Een afgeronde poging registreert niets meer: record aanroepen na finish() moet de antwoorden en de score ongemoeid laten.

Voer het programma uit en controleer of de afgedrukte score overeenkomt met de antwoorden erboven.