Samenwerkende objecten · oefening
Lees en teken samenwerking tussen objecten
Je hebt nu vijf klassen geschreven die samenwerken. Iemand die aan dit programma gaat meewerken zou ze alle vijf moeten lezen om te ontdekken hoe. Er is een snellere manier, en dat is de laatste vaardigheid die dit hoofdstuk leert: een ontwerp beschrijven zonder het te schrijven.
Een ontwerp op vijf regels
De notatie uit les 3 is voldoende voor de meeste gesprekken:
a Quiz has many Questions
a QuizAttempt has a Quiz
a QuizAttempt has many Responses
a Response has a Question
a ScoreReport has a QuizAttempt
Lees het als een kaart van wie aan wie iets kan vragen. Een ScoreReport kan bij een QuizAttempt, en via die poging bij een Quiz, maar een Question kan nergens bij. Vragen staan onderaan: ze antwoorden en stellen zelf nooit vragen.
Die laatste observatie is meer waard dan het lijkt. De objecten zonder uitgaande pijlen kun je zelfstandig testen, overal hergebruiken en met vertrouwen veranderen. Als een ontwerp zulke objecten niet heeft, hangt alles van alles af.
Hetzelfde ontwerp als afbeelding
Bij meer dan een handvol klassen leest een schets sneller. ‘Who holds whom?’ betekent ‘wie bewaart wie?’: het rapport bewaart één poging (‘holds one attempt’), de poging één quiz en meerdere antwoorden (‘holds one quiz and many responses’), de quiz meerdere vragen (‘holds many questions’) en elk antwoord één vraag (‘holds one question’). De vraag beantwoordt verzoeken en bewaart geen samenwerkingspartner (‘answers requests’, ‘holds no collaborator’). De tekst onderaan zegt dat pijlen van het bewarende object naar de directe samenwerkingspartner wijzen, nooit terug.

Pijlen wijzen van het bewarende object naar het object dat het bewaart. Dit is geen formele notatie en dat hoeft ook niet. Het enige doel is op één scherm passen en je dingen laten opmerken, zoals: Question heeft twee inkomende pijlen en geen uitgaande, en er is geen route terug omhoog van een Question naar de Quiz die die vraag bewaart.
Waar je in een tekening op let
Zodra een ontwerp is getekend, worden sommige problemen zichtbaar zonder dat je code leest.
Pijlen in beide richtingen. Als Quiz naar QuizAttempt wijst en QuizAttempt terugwijst naar Quiz, vraag je dan af of beide richtingen nodig zijn. De relatie kan wijzigingen aan de twee klassen aan elkaar koppelen. Objectverwijzingen vereisen niet dat beide klassen elkaar importeren, maar onnodige afhankelijkheden maken de latere indeling in modules moeilijker. Hoofdstuk 10 behandelt imports.
Eén object met pijlen naar alles. Dat is het god object uit les 5, zichtbaar als vorm.
Een lange keten. ScoreReport -> QuizAttempt -> Quiz -> Question is prima zolang elke stap alleen iets aan de buur vraagt. Het wordt een probleem als het rapport helemaal naar beneden gaat reiken. In code zie je dat als report.attempt.quiz.questions()[0].points.
Een object waar niets naar wijst. Controleer wie het aanmaakt en gebruikt. Het kan een ingang zijn, een niet-getekende aanroeper kan het gebruiken, of het kan overbodig zijn.
Een ontwerp toont Quiz met een pijl naar QuizAttempt, en QuizAttempt met een pijl terug naar Quiz. Wat suggereert dat?
Tekenen voordat je schrijft
Het nuttigste moment hiervoor is voordat er een klasse bestaat.
Neem de eis ‘een cursist kan een quiz opnieuw doen en we bewaren elke poging’. Schrijf de zelfstandige naamwoorden op en daarna de relaties:
a Learner has many QuizAttempts
a QuizAttempt has a Quiz
a QuizAttempt has a started_on day
Drie regels, en er is al een beslissing genomen: pogingen horen bij een cursist in plaats van bij een quiz. Dat heeft gevolgen, omdat ‘laat alles zien wat Mina deed’ nu makkelijk is en ‘laat iedereen zien die de eindtoets heeft gedaan’ nu een zoekopdracht is. Dit op papier opmerken kost een minuut. Het opmerken nadat je vier klassen hebt geschreven kost een middag.
De vuistregel: schets als je meer dan ongeveer drie klassen hebt, of telkens als je merkt dat je niet weet welk object iets zou moeten bewaren.
De oefening
Je krijgt een geschreven ontwerp en een reeks half afgemaakte klassen. Laat de code overeenkomen met het ontwerp en gebruik daarna het ontwerp om een vraag te beantwoorden over een wijziging die nog niemand heeft gemaakt.
Opdracht
Maak Learner af zodat de code overeenkomt met dit ontwerp:
a Learner has many QuizAttempts
a QuizAttempt has a Quiz
a QuizAttempt has many Responses
a Response has a Question
a Quiz has many Questions
Question, Quiz, Response en QuizAttempt zijn gegeven samenwerkingspartners. Learner(name) bewaart de publieke naam. start(quiz) maakt een nieuwe QuizAttempt voor die quiz, bewaart die en geeft die terug. attempt_count meldt hoeveel pogingen zijn gestart. best_score meldt de hoogste actuele pogingscore, of 0 als er geen pogingen zijn. history() geeft een nieuwe lijst met pogingen terug in de volgorde waarin ze zijn gestart; die lijst veranderen mag de geschiedenis van de cursist niet veranderen.
Lees de gegeven klassen naast de pijlen voordat je Learner implementeert. Zo blijft de oefening gericht op het interpreteren van een samenwerkingstekening, in plaats van vier eerdere lessen tegelijk opnieuw op te bouwen.
Volg de pijlen. Niets wijst van een Quiz terug naar een QuizAttempt, dus een quiz mag nooit pogingen of cursisten noemen. Niets wijst van een Question ergens naartoe, dus een vraag blijft antwoorden en vraagt zelf nooit iets.
Het programma drukt een korte geschiedenis af voor één cursist met twee pogingen voor dezelfde quiz.