0%

Samenwerkende objecten · oefening

Kies waar een verantwoordelijkheid hoort

Elke methode die je schrijft moet in een klasse staan, en vaak lijken twee of drie klassen een redelijke plek. Een verkeerde keuze maakt het programma niet kapot. Die maakt de volgende wijziging moeilijker, op een manier die onzichtbaar blijft tot je die probeert door te voeren.

Hier is een concrete beslissing. Een quiz moet kunnen melden of een cursist is geslaagd, met een regel zoals ‘minstens 60 procent van de beschikbare punten’.

Waar komt passed?

Drie aannemelijke plekken

Op Quiz: quiz.passed(attempt). De quiz kent het totaal aan beschikbare punten, dat de regel nodig heeft.

Op QuizAttempt: attempt.passed. De poging kent de score, die de regel ook nodig heeft.

Op ScoreReport: report.passed(). Rapporteren is wat een rapport doet.

Alle drie zouden werken. Toets elke keuze aan een vraag die niets met smaak te maken heeft: welk object zou moeten veranderen als de regel veranderde?

De slaaggrens is een eigenschap van de quiz. Bij een oefenquiz slaag je misschien met 50 procent en bij een eindtoets met 80. Het is geen eigenschap van één poging en zeker geen eigenschap van een rapport, dat een resultaat moet kunnen weergeven zonder er een oordeel over te hebben.

De grens hoort dus bij Quiz. Maar de vergelijking heeft een score nodig en die hoort bij een poging.

Een regel verdelen over de verantwoordelijke objecten

Try it

Twee kleine methoden in plaats van één. Quiz.is_passing(score) beheert de grens en de berekening. QuizAttempt.passed weet welke score het moet doorgeven.

Aanroepers krijgen de leeswijze die ze wilden, attempt.passed, en de slaaggrens kan naar 0.8 door één klasse aan te passen.

Merk op dat is_passing een score ontvangt in plaats van een poging. Een quiz hoeft niet te weten dat pogingen bestaan. Door dat zo te houden, kan dezelfde quiz een opgeslagen resultaat, een verwachte score of een oefenronde beoordelen.

Een nieuwe eis zegt dat een quiz met het kenmerk 'final' 80 procent vereist om te slagen, terwijl andere quizzen 60 vereisen. Welke klasse verandert?

Vragen die een methode op haar plek zetten

Als je twijfelt, geven deze meestal de doorslag:

  1. Welk object bevat de gegevens die de regel nodig heeft? Als één object ze allemaal heeft, begin daar.

  2. Welk object zou veranderen als de regel veranderde? De regel hoort bij het ding waarover die iets zegt.

  3. Hoe zou de methode heten? Als een goede naam een andere klasse moet noemen (calculate_attempt_score_for_quiz), staat de methode waarschijnlijk op de verkeerde plek.

  4. Betekent deze plek dat je door twee objecten heen moet reiken? Dan hoort de methode een stap dichterbij.

Het god object

De fout waartegen dit hoofdstuk je beschermt, heeft een naam. Een god object is een klasse die uiteindelijk alles beheert, meestal omdat het de eerste geschreven klasse was en elke nieuwe functie daar het makkelijkst kon worden toegevoegd.

Het ontstaat door een reeks afzonderlijk redelijke beslissingen. Quiz bewaart de vragen, dus de scoreberekening komt daar. De klasse heeft de scores, dus de rapportage komt daar. De klasse heeft het rapport, dus de opmaak komt daar. Geen enkele stap is overduidelijk verkeerd, en uiteindelijk is Quiz negenhonderd regels lang en raakt elke wijziging die klasse.

De signalen zijn het onthouden waard: een klasse die veel groter is dan de klassen eromheen, een klasse die overal geïmporteerd moet worden, een klasse waarvan de naam niet meer beschrijft wat die doet en methoden die drie niet-gerelateerde stukken toestand nodig hebben.

De oefening geeft je precies deze situatie, op een schaal die je in een paar minuten kunt herstellen.

Opdracht

Quiz is een god object geworden. De klasse bewaart de vragen en heeft daarnaast de scoreberekening, de slaagregel en de rapportopmaak verzameld.

Verplaats elke verantwoordelijkheid naar de klasse waar die hoort:

  • Quiz behoudt de vragen, total_points en de slaagregel als is_passing(score). De pass_mark blijft een argument bij het aanmaken.

  • QuizAttempt krijgt answer(question, given), score en passed, dat het aan de quiz vraagt.

  • ScoreReport krijgt summary(), dat "<learner>: <score> of <total>, <passed or failed>" teruggeeft.

Verwijder elke methode uit Quiz zodra de taak is verplaatst. Als je klaar bent, mag Quiz niets bevatten dat een cursist of een poging noemt.

Het aansturende programma in de startcode roept de nieuwe interface al aan, dus het werkt pas als je die methoden toevoegt. Als het af is, moet het dit afdrukken:

2 False
Mina: 2 of 5, failed