0%

Overerving en polymorfisme · oefening

Vervang een onhandige hiërarchie

Overerving is makkelijk in te zetten en moeilijk terug te draaien. Deze les gaat over herkennen dat je te ver bent gegaan en over wat je daarna doet.

Een hiërarchie die groeide

Hier is een familie die verstandig begon en bleef groeien:

class Question:
    ...

class GradedQuestion(Question):
    ...

class TimedGradedQuestion(GradedQuestion):
    ...

class TimedGradedShuffledQuestion(TimedGradedQuestion):
    ...

Elke klasse werd om een echte reden toegevoegd. Samen beschrijven ze een probleem dat de hiërarchie niet kan oplossen: wat gebeurt er als iemand een vraag met tijdslimiet wil die niet wordt beoordeeld, of een gehusselde vraag zonder tijdslimiet?

Elke combinatie van onafhankelijke mogelijkheden heeft een eigen klasse nodig en het aantal klassen verdubbelt bij elke nieuwe mogelijkheid. Met tijdslimiet, gehusseld en beoordeeld zijn geen drie soorten vragen. Het zijn drie dingen die een vraag afzonderlijk kan zijn.

De signalen

Je bent te ver gegaan als je dit ziet:

Namen die lijsten van bijvoeglijke naamwoorden zijn. TimedGradedShuffledQuestion is een klassenaam die toegeeft een combinatie te zijn.

Een subklasse die het meeste van wat die erft negeert. Als acht van de tien geërfde methoden niet van toepassing zijn, is de ‘is een’-relatie niet echt.

Overschrijvingen die een exceptie opgooien of niets doen. De LockedQuestion uit les 8 die reword weigert is een subklasse die niet als basis kan functioneren.

Diepte om de diepte. Elk extra niveau moet het gedeelde gedrag rechtvaardigen. Elk niveau is nog een plek waar een methode verborgen kan zitten, dus diepe hiërarchieën verdienen meer kritische aandacht dan ondiepe.

Een basis die blijft groeien. Als een subklasse toevoegen betekent dat je een attribuut aan de basis moet toevoegen dat alleen die subklasse gebruikt, is de basis een samenvoeging van de kinderen geworden.

Welke van deze signalen wijst er het sterkst op dat een hiërarchie is ontspoord?

Vervangen door compositie

De uitweg is het antwoord uit hoofdstuk 6: wat een vraag is blijft overerving en wat een vraag heeft wordt compositie. In het diagram betekent ‘Inheritance’ overerving en ‘Composition’ compositie. Links vermenigvuldigt elke nieuwe mogelijkheid het aantal klassen (‘each new feature multiplies classes’); rechts heeft de vraag een optionele samenwerkingspartner (‘has an optional collaborator’).

Compositie laat een TextQuestion optioneel een TimeLimit hebben, in plaats van het aantal overervingsklassen te laten groeien.
Try it

Eén vraagklasse, met of zonder tijdslimiet. Voeg husselen toe en dat is nog een optionele samenwerkingspartner, geen verdubbeling van de klassenlijst. De aanroeper maakt combinaties bij het aanmaken, in plaats van dat jij ze vooraf opsomt.

Wat je als overerving behoudt

Merk op dat TextQuestion(Question) is gebleven. Dat hoort ook.

Het verschil is dat een tekstvraag een werkelijke soort is: die verandert wat is_correct betekent en geen enkele vraag is tegelijk een tekstvraag en een numerieke vraag. Timing is geen soort. Een vraag heeft wel of geen tijdslimiet en dezelfde vraag kan morgen een andere hebben.

De werkbare vraag: is dit een soort ding of iets wat het heeft? Soorten die elkaar uitsluiten en het kerngedrag van het object veranderen zijn kandidaten voor subklassen. Mogelijkheden die vrij combineren zijn samenwerkingspartners.

Het achteraf veranderen

Een hiërarchie in werkende code ontwarren is refactoring en gaat in kleine stappen:

  1. kies één mogelijkheid die geen soort is, zoals timing;

  2. schrijf de kleine klasse die die beheert;

  3. geef de basis er een optioneel attribuut voor, standaard zonder waarde;

  4. verplaats het gedrag van die mogelijkheid uit de subklassen naar de nieuwe klasse;

  5. verwijder elke subklasse die alleen bestond om die mogelijkheid toe te voegen;

  6. houd de tests bij elke stap werkend.

De laatste stap is het belangrijkst en is het volledige onderwerp van hoofdstuk 13. Refactoring zonder tests is herschrijven en hopen.

Geen betoog tegen overerving

Het punt is niet dat overerving slecht is. Het is één hulpmiddel en dit hoofdstuk heeft het goed gebruikt: een kleine familie van vraagtypen, één niveau diep, die elk een werkelijk andere regel voor dezelfde bewerking leveren.

Het gaat mis als je overerving behandelt als de manier om alles te delen. Compositie combineert vrij en overerving niet, dus compositie is de standaard. Overerving gebruik je wanneer de ‘is een’-relatie klopt.

Opdracht

Zes klassen beschrijven hier twee soorten vragen en twee optionele mogelijkheden. Breng ze terug tot drie klassen en twee samenwerkingspartners.

Behoud TextQuestion en NumericQuestion als subklassen van Question. Een tekstvraag zijn verandert echt wat is_correct betekent en geen enkele vraag is beide.

Maak van timing en hints dingen die een vraag heeft. Schrijf een gevalideerde TimeLimit(seconds) die positieve seconden vereist en een gevalideerde Hint(text) die niet-lege tekst vereist. Geef Question optionele attributen timing en hint, beide standaard None, en behoud de validatie voor een niet-lege vraagtekst en niet-negatieve punten.

Question.describe() geeft "<prompt> (<points> points)" terug, gevolgd door een spatie en de beschrijving van elke aanwezige samenwerkingspartner, timing vóór hint.

Behoud de antwoordafspraak uit les 8: numerieke vragen geven False terug voor lege of niet-numerieke tekst.

Verwijder TimedTextQuestion, HintedTextQuestion en TimedHintedTextQuestion. Elke combinatie moet nu kunnen worden gemaakt door samenwerkingspartners door te geven, ook combinaties waarvoor niemand een klasse schreef.