← Back to CoursesStemExpert
StemExpert
Expert · Handboek · 35. Objectgericht programmeren en softwareontwerp
StemExpert · Brecht Corbeel · schoolium.me
StemExpert
E35. Objectgericht programmeren en softwareontwerp
EExpert · deel E7 · Informatica

Handboek · hoofdstuk 35Objectgericht programmeren en softwareontwerp

Software groeit door duidelijke contracten, vervangbare onderdelen en aantoonbaar gedrag

In klimaat.ino v5 werken meten, pompen, dimmen en alarmen al samen. De Pi voegt modellen en voorspellingen toe. Je verdeelt die software in objecten die hun toestand bewaken, test hun grenzen en maakt elke wijziging terugvindbaar.

10× uitleg4× uitgewerkt voorbeeld1× naslag1× verhaal1× het geheel3× code9 opdrachten in het werkboek± 11 lestijden
Een softwaregrens maakt een sensor verwisselbaar zonder de regelwet te herschrijven.
Een softwaregrens maakt een sensor verwisselbaar zonder de regelwet te herschrijven.
Na dit hoofdstuk
Uitleg · 35.1

Een object bezit samenhangende toestand

1/20

Een losse variabele temperatuur kan uit elke taak overschreven worden. Een sensorobject bewaart zijn kalibratie, laatste geldige meting en tijdstip samen. De klasse legt vast welke methoden die toestand mogen veranderen. Twee instanties van dezelfde klasse kunnen andere adressen en kalibraties hebben: hun identiteit en toestand zijn verschillend, hun gedrag volgt dezelfde definitie.

Een attribuut is opgeslagen informatie; een methode is een functie die via self de instantie kent. De constructor __init__ maakt een geldige begintoestand. Een invariant zoals ‘het maximaal vermogen is positief’ hoort na de constructor en na elke toegestane wijziging waar te blijven. Objectgericht ontwerpen draait om zulke invarianten en verantwoordelijkheden, niet om elk zelfstandig naamwoord in een klasse te veranderen.

objecttoestandgedrag
Sensor S1adres, offset, tijdlees, kalibreer
Regelaar kamerKp, Ki, integraalstap, reset
EnergiebeheerSoC, grenzen, modusplan, begrens
Uitleg · 35.2

Een gedeelde lijst kan alle sensoren besmetten

2/20

Een klasseattribuut wordt door instanties gedeeld. class Sensor: fouten=[] geeft alle sensoren dezelfde mutable lijst. Voeg je bij S1 een fout toe, dan ziet S2 die ook. Maak een eigen lijst in __init__: self.fouten=[]. Ook een standaardargument fouten=[] wordt eenmaal aangemaakt; gebruik None en maak binnen de functie een nieuwe lijst.

Python kent geen harde private attribuutgrens. Een voorloopunderscore betekent ‘intern, gebruik het contract’. Properties kunnen validatie afdwingen. Een onveranderlijke dataclass(frozen=True) is geschikt voor een meetrecord: tijd, waarde, eenheid en kwaliteit blijven bij elkaar. De dataclass garandeert niet automatisch fysieke geldigheid; die toets je expliciet.

keuzegevolg
self.offsetper instantie
klasse constante MAX_Wbewust gedeeld
klasse lijst foutengedeelde wijzigbare toestand
frozen meetrecordvelden niet gewoon opnieuw toewijzen
Uitgewerkt voorbeeld · 35.3

Een objectwissel zonder regelwetwissel

3/20
Twee temperatuuradapters leveren 18,0 °C met een geldig tijdstip.
Gegeven
  • De ene adapter gebruikt SHT31, de andere een testreeks
  • het contract is identiek
Gevraagd
  • Wat hoort gelijk te blijven en wat mag verschillen?
Oplossing
  1. 1
    De regelaar ontvangt hetzelfde meetrecord en produceert dezelfde warmtevraag: bij r=20 en Kp=200 is dat 400 W.
  2. 2
    De adapter mag verschillen in buscode, adres en interne kalibratie, zolang bereik, eenheid, leeftijd en foutgedrag blijven passen.
  3. 3
    De integratietest controleert naast het unit-resultaat ook dat de echte adapter een geldig record levert en niet blokkeert.
Antwoord
Vervangbaarheid wordt aan gedrag en timing getoetst, niet aan de klassenaam.
Uitleg · 35.4

Interfaces dragen betekenis, geen stekkervorm

4/20

Twee sensoren met een methode lees() zijn pas verwisselbaar als hun resultaten dezelfde betekenis hebben. Een contract benoemt eenheid, bereik, tijdreferentie, maximale leeftijd en foutgedrag. Een kelvinsensor die een gewoon getal teruggeeft kan syntactisch passen maar de verwarming onmiddellijk laten uitschakelen.

De les gebruikt Meting(waarde, tijd_s, eenheid). Een Protocol beschrijft voor typecontrole de verwachte methode; Python controleert deze typehint niet vanzelf tijdens uitvoering. De regelaar voert daarom runtimecontroles uit. In echte stationslogs is tijd_s een monotone leeftijdsklok binnen een sessie; de logger bewaart daarnaast een kalenderstempel in UTC+1. Een herstart-ID voorkomt dat twee monotone tijdlijnen worden verward.

Sensoradapterlees() → MetingRegelaarstap(r, nu) → WActuatoradapterschrijf(W)Supervisormodus + foutbeleidLoggerdata + versieTestadaptervaste meetreeks
Een sensor levert data; de supervisor beslist wat een sensorfout voor de bedrijfsmodus betekent.
Uitleg · 35.5

Samenstelling: een regelaar heeft een sensor

5/20

Een regelaar heeft een sensor en een actuator. Hij is geen sensor. Samenstelling past daarbij: de constructeur krijgt een sensorobject en gebruikt zijn interface. Dit heet dependency injection. De test kan een ProefSensor leveren en de installatie een SHT31-adapter zonder de regelwet te wijzigen.

Elke verantwoordelijkheid heeft één eigenaar. In i40 werd het pompconflict opgelost door één taak de pomp te laten sturen. Maak op de Pi geen tweede schrijver die rechtstreeks hetzelfde relais bestuurt. De Pi stuurt begrensde setpoints of voorstellen; de Mega blijft eigenaar van lokale actuatoren en veiligheidsmodi. Een interface die een voorstel accepteert, moet ook weigering, timeout en huidige modus terugmelden.

laagverantwoordelijkheidmag niet
hardwareadapteruitlezen en omzettenbedrijfsbeleid verzinnen
regelaarregelfout naar vraagnetwerk opnieuw verbinden
supervisormodus en fallbacknoodstop omzeilen
loggerbewijs bewarende pomp aanzetten
Uitgewerkt voorbeeld · 35.6

Een dubbele pompschrijver opsporen

6/20
De klimaatregeling en AI-taak willen beide relais POMP aansturen.
Gegeven
  • Regeling: reservoir te laag → pomp uit
  • AI: planten slap → extra pompbeurt
Gevraagd
  • Een architectuur die beide bedoelingen correct verwerkt
Oplossing
  1. 1
    Twee schrijvers geven racegedrag: de laatste write wint, ook als het reservoir leeg is.
  2. 2
    Maak één lokale pompcontroller. De AI levert alleen een verzoek met duur en vervaltijd; de controller controleert niveau, modus en bestaande cyclus.
  3. 3
    Een laag-niveauvoorwaarde weigert het verzoek. De weigering wordt met reden en request-ID gelogd, zodat de AI haar actie niet als uitgevoerd telt.
Antwoord
Één eigenaar met expliciete verzoeken vervangt twee concurrerende relaiswriters.
Uitleg · 35.7

Overerving: een afgeleid object moet zijn belofte houden

7/20

Overerving is passend als een afgeleid type overal bruikbaar blijft waar het basistype beloofd is. Een temperatuuradapter kan van een algemene sensoradapter erven als lezen, tijdstempels en fouten hetzelfde contract behouden. De subklasse mag geen extra preconditie toevoegen die oude gebruikers niet kennen.

Een basisklasse Actuator.schrijf(vermogen_W) vervangen door een subklasse die procenten verwacht schendt het contract. Ook een blokkerende netwerkvariant schendt een contract van maximaal 5 ms, al heeft ze dezelfde methodenaam. Vermijd diepe ervingsbomen. Een kleine interface met samenstelling maakt variaties in sensor, opslag en communicatietransport vaak eenvoudiger.

Overerving bespaart altijd code

Hergebruik zonder substitueerbaarheid koppelt wijzigingen aan onverwachte plaatsen. Kies een gedeelde helper of samenstelling als de betekenis van het gedrag verschilt.

Uitgewerkt voorbeeld · 35.8

Een contract maakt een fout zichtbaar

8/20
De P-regelaar gebruikt Kp = 200 W/K, setpoint 20 °C en vermogen 0–2000 W.
Gegeven
  • meting 18 °C op t = 10 s
  • nu = 11 s
  • maximale leeftijd 5 s
Gevraagd
  • Gevraagd vermogen en gedrag bij t = 16 s
Oplossing
  1. 1
    De leeftijd is 1 s: binnen het contract. De fout is 2 K en de vraag is 400 W.
  2. 2
    Op t = 16 s is dezelfde meting 6 s oud. De regelaar weigert haar; de supervisor kiest de vooraf ontworpen gedegradeerde modus.
  3. 3
    Een fout teruggeven verschilt van 0 W bevelen: 0 W zou de kamer zonder beoordeling laten afkoelen.
Antwoord
400 W bij verse data; expliciete fout bij veroudering.
Klopt dit? De 5 s-grens is het lescontract voor de snelle regelinput, niet de MQTT-telemetrieperiode.
Code · 35.9

Vervangbare meting, echte grensgevallen

9/20

Deze module draait zeven unit tests met extra subtests voor niet-eindige instellingen tijdens het bouwen. Het is een P-regelaar om de softwaregrenzen zichtbaar te maken; e39 levert de PI(D)-regelwet. De supervisor staat buiten deze unit en bepaalt de fallback. De constructor weigert ook NaN en oneindig; het setpoint en de klok krijgen hun eigen contractcontrole.

Pythonregelaar_contract.py73 regelsDownload
from dataclasses import dataclassfrom math import isfinitefrom typing import Protocolimport unittestimport io @dataclass(frozen=True)class Meting:    waarde: float    tijd_s: float    eenheid: str = "degC" class Sensor(Protocol):    def lees(self) -> Meting: ... class ProefSensor:    def __init__(self, waarde, tijd_s):        self.waarde, self.tijd_s = waarde, tijd_s    def lees(self):        return Meting(self.waarde, self.tijd_s) class Regelaar:    def __init__(self, sensor, kp=200.0, grens=2000.0, max_leeftijd=5.0):        if (not all(isfinite(x) for x in (kp, grens, max_leeftijd))                or kp < 0 or grens <= 0 or max_leeftijd <= 0):            raise ValueError("ongeldige instelling")        self.sensor = sensor             # samenstelling, geen Sensor-subklasse        self.kp, self.grens = kp, grens        self.max_leeftijd = max_leeftijd    def stap(self, setpoint, nu):        if not isfinite(setpoint) or not -30 <= setpoint <= 60 or not isfinite(nu):            raise ValueError("ongeldig setpoint of tijdstip")        m = self.sensor.lees()        if (m.eenheid != "degC" or not isfinite(m.waarde) or not isfinite(m.tijd_s)                or not -30 <= m.waarde <= 60                or not 0 <= nu - m.tijd_s <= self.max_leeftijd):            raise ValueError("meting onbruikbaar; supervisor kiest veilige modus")        return min(self.grens, max(0.0, self.kp * (setpoint - m.waarde))) class TestRegelaar(unittest.TestCase):    def test_normaal(self):        self.assertEqual(Regelaar(ProefSensor(18,10)).stap(20,11), 400)    def test_begrenzing(self):        self.assertEqual(Regelaar(ProefSensor(0,10)).stap(20,11), 2000)        self.assertEqual(Regelaar(ProefSensor(23,10)).stap(20,11), 0)    def test_oud(self):        with self.assertRaises(ValueError):            Regelaar(ProefSensor(18,10)).stap(20,16)    def test_nan(self):        with self.assertRaises(ValueError):            Regelaar(ProefSensor(float("nan"),10)).stap(20,11)    def test_eenheid(self):        class Kelvin:            def lees(self): return Meting(291.15,10,"K")        with self.assertRaises(ValueError):            Regelaar(Kelvin()).stap(20,11)    def test_instellingen(self):        for naam in ("kp", "grens", "max_leeftijd"):            for waarde in (float("nan"), float("inf"), -float("inf")):                with self.subTest(naam=naam, waarde=waarde), self.assertRaises(ValueError):                    Regelaar(ProefSensor(18,10), **{naam:waarde})    def test_invoer(self):        for r,nu in [(float("nan"),11),(float("inf"),11),(20,float("nan")),(61,11)]:            with self.subTest(r=r,nu=nu), self.assertRaises(ValueError):                Regelaar(ProefSensor(18,10)).stap(r,nu) log = io.StringIO()suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestRegelaar)resultaat = unittest.TextTestRunner(stream=log).run(suite)print("tests", resultaat.testsRun, "fouten", len(resultaat.errors),      "mislukt", len(resultaat.failures))print("warmtevraag", Regelaar(ProefSensor(18,10)).stap(20,11), "W")assert resultaat.wasSuccessful()
tests 7 fouten 0 mislukt 0
warmtevraag 400.0 W
Sensoradapterlees() → MetingRegelaarstap(r, nu) → WActuatoradapterschrijf(W)Supervisormodus + foutbeleidLoggerdata + versieTestadaptervaste meetreeks
Een sensor levert data; de supervisor beslist wat een sensorfout voor de bedrijfsmodus betekent.
Code · 35.10

Overerving uitvoeren, samenstelling behouden

10/20

De gekalibreerde sensor erft alleen het passende sensorcontract: waarde in °C met hetzelfde meettijdstip. Hij roept de basisconstructor en de basismethode via super() aan, voegt de offset toe en behoudt eenheid en leeftijd. De Regelaar blijft door samenstelling dezelfde sensorinterface gebruiken. Het verschil van 60 W tussen ruw en geijkt volgt uit 0,3 K × 200 W/K; overerving verandert de regelwet niet.

Pythonsensor_overerving.py54 regelsDownload
from dataclasses import dataclassfrom math import isfinitefrom typing import Protocolimport unittestimport io @dataclass(frozen=True)class Meting:    waarde: float    tijd_s: float    eenheid: str = "degC" class Sensor(Protocol):    def lees(self) -> Meting: ... class ProefSensor:    def __init__(self, waarde, tijd_s):        self.waarde, self.tijd_s = waarde, tijd_s    def lees(self):        return Meting(self.waarde, self.tijd_s) class Regelaar:    def __init__(self, sensor, kp=200.0, grens=2000.0, max_leeftijd=5.0):        if (not all(isfinite(x) for x in (kp, grens, max_leeftijd))                or kp < 0 or grens <= 0 or max_leeftijd <= 0):            raise ValueError("ongeldige instelling")        self.sensor = sensor             # samenstelling, geen Sensor-subklasse        self.kp, self.grens = kp, grens        self.max_leeftijd = max_leeftijd    def stap(self, setpoint, nu):        if not isfinite(setpoint) or not -30 <= setpoint <= 60 or not isfinite(nu):            raise ValueError("ongeldig setpoint of tijdstip")        m = self.sensor.lees()        if (m.eenheid != "degC" or not isfinite(m.waarde) or not isfinite(m.tijd_s)                or not -30 <= m.waarde <= 60                or not 0 <= nu - m.tijd_s <= self.max_leeftijd):            raise ValueError("meting onbruikbaar; supervisor kiest veilige modus")        return min(self.grens, max(0.0, self.kp * (setpoint - m.waarde)))  class GekalibreerdeProefSensor(ProefSensor):    def __init__(self, waarde, tijd_s, offset):        super().__init__(waarde, tijd_s)        if not isfinite(offset): raise ValueError("offset niet eindig")        self.offset = offset    def lees(self):        m = super().lees()        return Meting(m.waarde+self.offset, m.tijd_s, m.eenheid) ruw = ProefSensor(18.3,10)geijkt = GekalibreerdeProefSensor(18.3,10,-0.3)for sensor in (ruw, geijkt):    print(type(sensor).__name__, Regelaar(sensor).stap(20,11), "W")assert abs(Regelaar(geijkt).stap(20,11)-400)<1e-10
ProefSensor 339.9999999999999 W
GekalibreerdeProefSensor 400.0 W
Uitleg · 35.11

Refactoren naar bestanden met gerichte imports

11/20

In een echte projectmap staat hab_meet.py met het immutable record, hab_vraag.py met de zuivere regelkern, adapters/sht31.py met I²C en app.py als samensteller. De regelkern importeert de meetdefinitie, nooit omgekeerd. Hardwareadapters importeren het contract; het contract importeert geen hardwarebibliotheek. Zo ontstaan geen cirkelimports.

Bij refactoring bewaar je eerst het bestaande gedrag in tests, verplaats je één verantwoordelijkheid en pas je de imports aan. Daarna draai je dezelfde tests. Wijzig tijdens die stap geen regelinstellingen: een verplaatsing en een gedragswijziging in één diff maken de oorzaak van een regressie onduidelijk. De onderstaande uitvoering gebruikt echte Python-imports tussen in-geheugenmodules; dezelfde bron wordt als bijlage getoond voor fysieke bestanden.

bestandimporteertdoel
hab_meet.pydataclassesdatacontract
hab_vraag.pyhab_meetzuivere kern
adapters/sht31.pyhab_meet + hardwarelibraryapparaatgrens
app.pykern en adapterssamenstelling
Code · 35.12

Een modulegrens werkelijk uitvoeren

12/20

Het demo-laadmechanisme registreert twee modules tijdelijk en herstelt de importomgeving achteraf. De gewone installatie gebruikt dezelfde imports uit bestanden. De uitvoer toont 400 W. Dit minimale voorbeeld demonstreert afhankelijkheidsrichting; de uitgebreide finite- en leeftijdscontroles blijven in regelaar_contract.py.

Pythonmodule_demo.py22 regelsDownload
# In een echte map staan de eerste twee bronnen in aparte .py-bestanden.# Hier worden dezelfde modules in geheugen geladen voor reproduceerbare uitvoer.import sysimport typesimport importlib bronnen = {    "hab_meet": "from dataclasses import dataclass\n@dataclass(frozen=True)\nclass Meting:\n    waarde: float\n    tijd_s: float\n",    "hab_vraag": "from hab_meet import Meting\ndef vraag(m: Meting, r=20.0):\n    return max(0.0,min(2000.0,200.0*(r-m.waarde)))\n"}oud = {naam:sys.modules.get(naam) for naam in bronnen}try:    for naam,bron in bronnen.items():        module = types.ModuleType(naam)        sys.modules[naam] = module        exec(bron,module.__dict__)    meten = importlib.import_module("hab_meet")    regelen = importlib.import_module("hab_vraag")    print("module-invoer", regelen.vraag(meten.Meting(18.0,10)), "W")finally:    for naam,vorige in oud.items():        if vorige is None: sys.modules.pop(naam,None)        else: sys.modules[naam] = vorige
module-invoer 400.0 W
Uitleg · 35.13

Unit tests: bewijs van gevallen, geen bewijs van alles

13/20

Een goede test maakt een fout zichtbaar en vertelt welk contract faalt. Normale invoer alleen is zwak: test minimum, maximum, net erbuiten, oude tijd, toekomsttijd, NaN, ontbrekende eenheid en sensoruitzondering. Een grens van leeftijd ≤ 5 s vraagt tests op 5 en net boven 5 s. NaN is bijzonder: gewone vergelijkingen leveren geen betrouwbare bereiktoets; isfinite maakt de bedoeling expliciet.

Een testdouble levert vaste invoer; een fake benadert een echte afhankelijkheid, een mock controleert interacties. Test bij voorkeur waarneembaar gedrag, niet private velden. Een unit test bewijst geen bedrading, timing of realistisch procesgedrag: integratietests verbinden onderdelen, systeemtests toetsen eisen en validatie toont bruikbaarheid voor bewoners.

testdetecteertmist mogelijk
unitfoute begrenzingkapotte SSR
integratieverkeerde eenheid adapterreële deadline
hardware-in-lustimer en signaalpadlangzame veroudering
bewonerproefbedieningsprobleemzeldzame faalwijze
Uitleg · 35.14

Testbaarheid verandert het ontwerp

14/20

Een regelaar die zelf de klok leest, een sensor opent en een netwerkbericht stuurt, is moeilijk reproduceerbaar. Geef tijd en meting via interfaces. Een simulatieklok kan precies een timeoutgrens aanbieden. Maak de kern zo veel mogelijk deterministisch: dezelfde invoer en toestand geven dezelfde uitvoer.

Bewaar de PID-integraal in het regelaarobject; log de overgang die hem reset. Beperk toegang tot gedeelde toestand en bepaal wie waarden schrijft. Bij gelijktijdige processen op Linux kan een meetrecord halverwege veranderd worden. Een onveranderlijk record of expliciete wachtrij maakt het overdrachtsmoment zichtbaar. Een test die uitsluitend dezelfde formule kopieert in het verwachte resultaat kan dezelfde fout herhalen; gebruik onafhankelijke handberekende gevallen.

een observeerbaar contract met fysische grenzen
Uitgewerkt voorbeeld · 35.15

Een staged wijziging reconstrueren

15/20
Een bestand heeft eerst versie A. Je wijzigt naar B, doet git add en wijzigt daarna naar C.
Gegeven
  • HEAD: A
  • index: B
  • werkmap: C
Gevraagd
  • Betekenis van beide diffs en de volgende commit
Oplossing
  1. 1
    git diff vergelijkt C met B en toont alleen de laatste werkmapwijziging.
  2. 2
    git diff --staged vergelijkt B met A en toont de geselecteerde commitwijziging.
  3. 3
    git commit legt B vast; C blijft als lokale wijziging aanwezig. Voor C als bedoelde release moet je opnieuw selecteren en toetsen.
Antwoord
Werkmap, index en geschiedenis zijn drie afzonderlijke momenten.
Uitleg · 35.16

Git: drie plaatsen, één gecontroleerde wijziging

16/20

Git onderscheidt de werkmap, de index (staging area) en de commitgeschiedenis. git diff toont werkmap tegenover index; git diff --staged toont wat in de volgende commit komt. git add selecteert een momentopname: latere edits zitten niet automatisch in die selectie. git commit legt de geselecteerde inhoud met een toelichting vast.

Een branch is een verplaatsbare verwijzing naar een commit. Maak een tak voor een verandering, toets het gedrag en laat een tweede lezer het diff beoordelen. Bij een conflict vergelijkt de integrator de bedoelingen en runt hij opnieuw tests; conflictmarkers verwijderen bewijst geen correcte integratie. Gebruik voor een gedeelde fout een nieuwe herstelcommit (git revert), zodat de geschiedenis terugvindbaar blijft.

stapvoorbeeldcommando
inspecteergit status; git diff
maak werktakgit switch -c sensor-leeftijd
selecteergit add regelaar.py tests/test_regelaar.py
review selectiegit diff --staged
toetspython -m unittest discover
leg vastgit commit -m 'Weiger verouderde temperatuurmetingen'
controle geschiedenisgit log --oneline
Uitleg · 35.17

Een versie is meer dan alleen de broncode

17/20

Een reproduceerbare stationsrelease benoemt commit-ID, configuratie, kalibratieversie, modelbestand, dataselectie en testverslag. Een broncodecommit zegt niets over een later overschreven modelbestand. Geef elk model een checksum en beschrijf welke sensor- en softwareversies ermee compatibel zijn.

Geheime sleutels horen niet in git. Een genegeerd bestand dat vroeger al gevolgd werd, blijft gevolgd; .gitignore maakt een gelekt geheim niet ongedaan. Trek een gelekte sleutel in en herstel de toegang. Een release op de Pi krijgt een terugrolpad naar een geteste vorige versie; lokale veiligheid blijft tijdens update bij de microcontroller. Een rollback van software kan ongeschikt zijn als het databankformaat al gewijzigd is: test de migratie en terugweg samen.

manifestveldvoorbeeld
codecommit-ID
kalibratieS1-versie 3
modelwarmte-v2 + SHA-256
configuratievermogen 2000 W, grenzen
bewijsunit-, integratie- en systeemtestverslag
Verhaal · 35.18

Softwaregeschiedenis: Simula maakte objecten een ontwerpmiddel

18/20

Het Noorse Computing Center beschrijft Simula als een taal ontwikkeld door Ole-Johan Dahl en Kristen Nygaard voor simulaties en als een grondslag van objectgericht programmeren. Een simulatie bevat vele gelijktijdig gedachte entiteiten met eigen toestand: dat helpt begrijpen waarom objecten meer zijn dan een nieuwe syntaxis rond functies.

De stationssoftware profiteert van hetzelfde idee: een buffervat, sensor en regelaar hebben andere toestand en verschillende levensduur. Hun grenzen moeten wel aan de echte afhankelijkheden beantwoorden. Bron: Norwegian Computing Center: geschiedenis; taalgedrag gecontroleerd in Python: classes, tests in unittest, versiebeheer in Pro Git, geraadpleegd 30-09-2026.

simulatieobjecteigen toestand
buffervatenergie en temperatuur
sensoroffset en laatste record
regelaarintegraal en vorige meting
Naslag · 35.19

Ontwerpbeslissingen zichtbaar houden

19/20
vraag bij reviewbewijs
Wie schrijft elke actuator?één eigenaar in architectuur
Welke eenheid levert de adapter?contract en test
Welke fout gaat naar welke modus?supervisortabel
Welke versie draaide?manifest en log
Kan hardware vervangen worden?testdouble via hetzelfde contract
Het geheel · 35.20

De software sluit het V-model

20/20

Goede klassen verbinden het procesmodel, meetcontract en testbewijs. Een systeemgrens die helder is in software moet ook terugkomen in interfaces, eisen en storingsanalyse.

v5 is de eindversie van de lokale module.

Een modelobject rekent zonder hardwaretoegang.

Transport is een adapter met foutcontract.

Eisen verwijzen naar tests en versiebeheer.

Habitat

De Pi krijgt adapters, een modelkern, predictor, planner, supervisor en logger. De Mega houdt exclusieve eigendom van lokale actuatoren. Nieuwe code wordt als manifest met toetsbewijs aangeboden.

Samenvatting

In het kort

Begrippen

Wat je nu kent

klasse
Een type dat toestand en bijbehorende bewerkingen bundelt.
object
Een concrete instantie met eigen toestand en identiteit.
samenstelling
Een object gebruikt andere objecten als onderdelen.
interfacecontract
De afgesproken invoer, uitvoer, fouten en betekenis van een bewerking.
unit test
Een herhaalbare toets van een afgebakend softwareonderdeel met gecontroleerde afhankelijkheden.
Aan de slag in het werkboek9 opdrachten, van oefenen tot uitdagen