← Python/Interfaces en typing
Les 1 van 8
MODULE 08 · 01◷ 29 min

Een object gebruiken om wat het kan

Na deze les kun je
  • Met verschillende onafhankelijke objecten dezelfde methode gebruiken.
  • Argumenten en resultaten zonder onbedoelde omzetting doorgeven.
  • Een adapter maken en alleen de afgesproken fout afhandelen.

Een melding kan naar een scherm, een logboek of een lokaal testobject gaan. De functie die de melding bezorgt, hoeft niet te weten hoe elk doel vanbinnen werkt. Zij heeft vooral een duidelijke afspraak nodig over de bewerking die zij mag aanroepen.

Gedrag als afspraak

Bij duck typing gebruikt code het gedrag dat een object aanbiedt. Heeft een object de vereiste methode en werkt die volgens de afspraak, dan kan het meedoen. Een gemeenschappelijke ouderklasse is daarvoor niet nodig.

Een interface is hier de beschrijving van dat verwachte gedrag: welke methode bestaat, welke argumenten zij ontvangt en wat zij teruggeeft. Python vereist voor deze eerste vorm geen apart interfacebestand. Je kunt de afspraak in uitleg, functienamen en later ook typeannotaties vastleggen.

Het voorbeeld verwacht alleen een methode read(). De twee klassen bewaren hun gegevens verschillend, maar de aanroepende functie gebruikt dezelfde bewerking. De echte retourwaarde van die methode bepaalt de uitkomst.

Twee onafhankelijke lezers bieden dezelfde bewerking
class FixedReading:
    def read(self):
        return "18"

class LastReading:
    def __init__(self, values):
        self.values = values
    def read(self):
        return self.values[-1]

def show_reading(source):
    return "Meting: " + source.read()

print(show_reading(FixedReading()))
print(show_reading(LastReading(["16", "21"])))
Verwachte uitvoer
Meting: 18
Meting: 21

De functie kijkt niet naar de klassenaam en opent geen netwerkverbinding. Zo kun je een echte component later vervangen door een klein lokaal testobject. Dat object moet dan wel hetzelfde relevante gedrag aanbieden.

Argumenten en resultaten doorgeven

In de opdrachten heet de methode send(message). deliver geeft de ontvangen tekst ongewijzigd door en geeft precies het resultaat van send terug. Dat resultaat hoeft niet altijd tekst te zijn: een doel kan bijvoorbeeld een volgnummer of een ander object teruggeven.

Dat maakt return sender.send(message) anders dan alleen de methode uitvoeren. Zonder return ontvangt de aanroeper None. Met str(...) zou je het resultaat juist veranderen, terwijl deze afspraak vraagt om het te bewaren.

send_all past dezelfde afspraak op meerdere doelen toe. De volgorde van de ontvangen doelen bepaalt de volgorde van de resultaten. Een lege lijst geeft een lege resultatenlijst; er hoeft dan niets verstuurd te worden.

Gedrag uitbreiden met een adapter

Een adapter ontvangt een bestaand object en biedt zelf een passende methode aan. In het tweede voorbeeld voegt zo'n object een eenheid toe aan een meting. De oorspronkelijke lezer blijft verantwoordelijk voor het lezen.

Een adapter gebruikt de ontvangen lezer
class FixedReading:
    def read(self):
        return "18"

class ReadingWithUnit:
    def __init__(self, source, unit):
        self.source, self.unit = source, unit
    def read(self):
        return self.source.read() + " " + self.unit

original = FixedReading()
adapted = ReadingWithUnit(original, "graden")
print(adapted.read())
print(original.read())
print(adapted.source is original)
Verwachte uitvoer
18 graden
18
True

Je herkent compositie uit de vorige module: de adapter bewaart een verwijzing naar een ander object. Hij hoeft daarvan niet te erven. De buitenkant blijft voor de aanroeper een object met een read()-methode.

Maak straks PrefixedSender op dezelfde manier, maar voor send(message). Bewaar de ontvangen verzender en het voorvoegsel. Voeg het voorvoegsel alleen aan het doorgegeven bericht toe; het antwoord van de verzender blijft ongewijzigd.

Veelgemaakte fout: de methode bewaren zonder aanroepen

sender.send is een gebonden methode, terwijl sender.send(message) die methode werkelijk uitvoert. De eerste vorm bezorgt nog geen bericht. Alleen de naam van een methode afdrukken bewijst dus niet dat het doel haar heeft ontvangen.

Een controle op één concrete klasse zou daarnaast andere passende objecten uitsluiten. Gebruik hier de afgesproken methode rechtstreeks. Een object zonder die methode veroorzaakt gewoon een AttributeError.

Alleen de bedoelde fout afhandelen

try_deliver voegt een kleine foutafspraak toe. Bij succes geeft hij (True, resultaat); alleen bij ConnectionError geeft hij (False, fouttekst). Hij voert nog steeds dezelfde echte methode uit.

Andere fouten worden niet stil omgezet in succes of een lege waarde. Een ValueError uit een doel en een ontbrekende methode mogen doorlopen. Daardoor blijft een verkeerd aangesloten object zichtbaar als programmeerfout.

AAN DE SLAG 0/4
1

De ontvangen methode werkelijk gebruiken

Vul deliver(message, sender) aan: roep sender.send(message) precies eenmaal aan en geef precies dat resultaat terug. Bewaar de tekst, het type en de identiteit van een resultaat; eis geen concrete verzenderklasse.

2

Meerdere doelen in hun volgorde bedienen

Maak send_all(message, senders): roep iedere ontvangen verzender eenmaal aan, ook als hetzelfde object vaker voorkomt. Geef de resultaten als nieuwe lijst in dezelfde volgorde terug, laat senders intact en geef bij lege invoer een lege lijst.

3

Een verzender aanpassen met compositie

De constructor van PrefixedSender is gegeven. Vul send(message) aan: stuur self.prefix + message naar self.sender.send en geef het ontvangen resultaat ongewijzigd terug. Ook een leeg voorvoegsel of bericht is geldig; verander de eigen prefix niet.

4

Alleen ConnectionError als bezorgfout behandelen

Maak try_deliver(message, sender). Roep de echte send-methode aan en geef bij succes (True, resultaat). Geef alleen bij ConnectionError of een subklasse daarvan (False, str(fout)); laat andere fouten ongewijzigd doorlopen.

Onthoud dit

  • Een interface beschrijft bruikbaar gedrag, zonder verplichte gezamenlijke ouderklasse.
  • Geef argumenten en resultaten door volgens de afgesproken betekenis.
  • Compositie laat een adapter het gedrag van een ander object gebruiken.

Hierna maak je zulke afspraken beter leesbaar met echte typeannotaties.

main.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.