0%

Overerving en polymorfisme · oefening

Overschrijf gedrag en breid het uit

Elke subklasse in de vorige les leverde een eigen is_correct. Dat heet overschrijven: een subklasse definieert een methode die de basis al heeft en de versie van de subklasse wordt uitgevoerd.

Er is nog iets wat een subklasse kan doen en het verschil doet ertoe.

Vervangen of aanvullen

describe overschrijven vervangt de basisversie volledig:

Try it

De puntenwaarde is uit de beschrijving verdwenen. De subklasse heeft de hele zin herschreven en elke verbetering die vanaf nu aan de basisversie wordt gemaakt gaat eraan voorbij.

Vaak wil je de basisversie plus iets:

Try it

super().describe() roept de basisversie aan vanuit de overschrijving en de subklasse voegt iets toe aan wat terugkomt. De punten verschijnen weer en blijven verschijnen als de basis verandert hoe die ze verwoordt.

Dit is dezelfde super() als in __init__, met dezelfde taak: voer de basisversie van deze methode uit. __init__ is alleen het geval dat je eerst tegenkomt.

De describe van een subklasse roept super().describe() aan en voegt iets toe aan het resultaat. Wat levert dat op vergeleken met het herschrijven van de hele string?

Waar elke methode vandaan komt

Bij een familie van klassen heeft ‘welke versie wordt uitgevoerd?’ een betrouwbaar antwoord nodig. Python kijkt eerst naar de eigen klasse van het object, dan naar de basis, dan naar de basis van de basis en stopt bij de eerste overeenkomst.

Try it

summary is alleen op Question gedefinieerd en roept self.describe() aan. De versie van TextQuestion wordt uitgevoerd, omdat self een TextQuestion is en het zoeken daar begint.

Het is de moeite waard hierbij stil te staan, omdat een basisklasse hierdoor meer nut heeft dan alleen code delen. Question.summary werd geschreven zonder te weten welke soorten vragen ooit zouden bestaan en roept toch de juiste describe voor elke soort aan. Een basisklasse kan de vorm van een bewerking definiëren en het variabele deel aan de subklassen overlaten.

Verantwoord overschrijven

Twee regels voorkomen dat een overschrijving de aanroepende code verrast.

Accepteer wat de basis accepteert. Als Question.is_correct(response) één argument ontvangt, moet een subklasseversie met één argument werken. Een subklasse die een extra argument vereist maakt elke aanroeper stuk die voor de basis is geschreven.

Geef terug wat de basis teruggeeft. Als is_correct True of False teruggeeft, zal een subklasse die "yes", een aantal punten of None voor ‘onbeantwoord’ teruggeeft door if question.is_correct(...) gaan en zich gedragen op manieren die niemand verwachtte.

Try it

Geen fout. Alleen Summary: None dat ergens op een scherm verschijnt. De les na de volgende gaat volledig over dit soort problemen.

Toestand én gedrag uitbreiden

__init__ volgt hetzelfde patroon en nu kun je dat zien als één geval van een algemene regel:

def __init__(self, prompt, points, tolerance):
    super().__init__(prompt, points)     # what every question needs
    self.tolerance = tolerance           # what this kind adds

Roep eerst de basis aan en voeg dan toe. Je eigen instelling doen voordat je super().__init__ aanroept is toegestaan en soms nodig, maar betekent dat een deel van het object bestaat voordat de basis klaar is. Dat is het probleem van een half opgebouwd object uit hoofdstuk 5, via een nieuwe route.

Opdracht

Elke subklasse herschrijft hier de hele beschrijving, waardoor een wijziging in de basisformulering nooit een van de subklassen zou bereiken.

Laat alle drie de basisbeschrijving uitbreiden in plaats van vervangen. Elke describe() moet super().describe() aanroepen en het eigen deel toevoegen:

  • TextQuestion: "<base> Type your answer."

  • ChoiceQuestion: "<base> Options: A, B, C." met de eigen opties

  • NumericQuestion: "<base> Answer within 0.2." met de eigen tolerantie

De basisformulering is "<prompt> (<points> points)" en geen van de subklassen mag die opmaak bevatten. De beoordelaar verandert de basisformulering en controleert of alle drie volgen. Dat is precies waarom je het zo doet.

Voeg daarna alleen aan de basis summary() toe, dat "Summary: <description>" teruggeeft. Eén keer geschreven moet die voor elke soort de juiste beschrijving opleveren.