0%

Samenwerkende objecten · oefening

Bouw heeft-een-relaties

De Quiz die je schreef bevat Question-objecten. De Attempt uit hoofdstuk 5 bevat resultaten. Zolang er maar één van elk was, hadden die constructies geen naam nodig. Maar een programma met vijf klassen heeft een manier nodig om te bespreken hoe ze samenhangen.

De relatie is in beide gevallen dezelfde: heeft-een. Een quiz heeft vragen. Een poging heeft resultaten.

Een ontwerp hardop lezen

Probeer de onderdelen in gewone zinnen te beschrijven met alleen ‘heeft een’ en ‘is een’:

a Quiz         has many  Questions
a QuizAttempt  has a     Quiz
a QuizAttempt  has many  Responses
a Response     has a     Question

Dat is een ontwerp. Het past op vier regels, iemand die geen Python kent kan het lezen en het legt iets vast: elke pijl geeft aan welk object een verwijzing naar welk ander object bewaart, en dus welk object het andere om dingen kan vragen.

Objecten op deze manier opbouwen uit andere objecten heet compositie. Het is de standaardmanier om een programma te structureren en blijft dat gedurende de rest van deze cursus. Hoofdstuk 9 introduceert ‘is een’-relaties, die in minder situaties bruikbaar zijn dan de meeste inleidingen suggereren.

Het opschrijven

Try it

Response vraagt het aan de vraag wanneer het antwoord binnenkomt en bewaart vervolgens dat oordeel en de punten die op dat moment beschikbaar zijn. Hoofdstuk 5 gebruikte dezelfde momentopnameregel: een vraag later veranderen mag een antwoord dat al is gegeven niet herschrijven.

Vier klassen, en de vier regels van het ontwerp hierboven komen er één op één mee overeen. QuizAttempt.__init__ ontvangt een Quiz en bewaart die. Response.__init__ ontvangt een Question en bewaart die. Elk object bewaart de dingen die het heeft.

Een verwijzing, geen kopie

attempt.quiz en de variabele quiz verwijzen naar hetzelfde object. Er is niets gekopieerd:

Try it

Door via quiz een vraag toe te voegen, veranderde wat attempt.quiz meldt, omdat er maar één quiz is. Dit is het idee van twee namen voor één object uit hoofdstuk 4, nu verspreid over twee klassen.

Deze versie volgt bewust de actuele quiz, dus een nieuw toegevoegde vraag telt ook mee als resterend. Het project maakt voor historische rapporten een andere keuze door de quizgegevens bij het begin vast te leggen. Dat is ook waarom de vorige les voorzichtig was met het weggeven van interne lijsten: gedeelde verwijzingen werken twee kanten op.

Een QuizAttempt bevat een Quiz. Wat betekent dat in het geheugen?

Wat elke klasse niet meer hoefde te weten

Kijk wat QuizAttempt.score doet. Die telt response.earned op en stopt.

Die weet niet dat een antwoord alleen punten oplevert als het juist is. Die weet niet dat de punten van een vraag komen. Die weet niet hoe een vraag de juistheid bepaalt. Elk van die gegevens staat in precies één klasse en de klasse erboven vraagt er gewoon om.

Vergelijk dat met hoe één grote klasse eruit zou zien: een Quiz met parallelle lijsten van vraagteksten, antwoorden, punten, gegeven antwoorden en verdiende waarden, waarbij elke methode meerdere lijsten tegelijk doorloopt en hun lengtes gelijk houdt. Dat is het ontwerp waarmee hoofdstuk 1 begon, en elke ‘heeft een’-regel hierboven is een relatie die vroeger een overeenkomende index was.

De grens trekken

De nuttige vraag als je twijfelt of een klasse een ander object moet bewaren of alleen de gegevens ervan: heeft het kleinere ding eigen gedrag?

Response bevat een Question in plaats van een vraagtekststring, omdat het is_correct moet aanroepen. Als een antwoord alleen de vraagtekst hoefde weer te geven, zou het bewaren van de vraagtekst redelijk en eenvoudiger zijn.

De oefening bouwt de klassen Response en QuizAttempt boven op de Quiz die je al hebt.

Opdracht

Maak de klasse boven de gegeven Response af en volg daarbij het ontwerp:

a Response     has a  Question
a QuizAttempt  has a  Quiz, and has many Responses

Response is gegeven. Die vraagt het één keer aan de vraag wanneer een antwoord binnenkomt en bewaart vervolgens momentopnamen van de juistheid en de verdiende punten, zodat een latere wijziging aan de vraag de geschiedenis niet herschrijft.

Bouw QuizAttempt: die bewaart de quiz en de cursist. answer(question, given) registreert één Response. score telt op wat de antwoorden hebben verdiend, correct_count telt de juiste antwoorden en remaining meldt hoeveel vragen van de quiz nog niet zijn beantwoord. In dit kleine model beantwoordt de aanroeper elke vraag in de quiz maximaal één keer. De oefening hoeft herhaalde antwoorden of vragen buiten de quiz niet te weigeren.

Laat elke klasse onwetend over de regels van de andere klassen. QuizAttempt mag nooit een gegeven antwoord met een juist antwoord vergelijken en Response mag nooit binnen in de quiz kijken.