Johnny Hogenbirk: Artikelen, code-snippets, etc.
Home
Lang leve classes - 07-02-2020
Inleiding
Objectgeoriënteerd programmeren is een manier om je code op te zetten. Het wordt ook wel een architectuur genoemd: hoe structureer je de code.Objectgeoriënteerd programmeren is een veel gebruikte manier van programmeren, met sommige talen kun je alleen maar objectgeoriënteerd programmeren. Het is een middel om code goed/beter te structureren. Maar daarmee is niet gezegd dat de code per definitie altijd beter gestructureerd is. Sterker nog, er is zoveel spaghetti OO-code dat er inmiddels veel tegenstanders zijn. Dat is wel jammer, want in de basis kun je met Objectgeoriënteerd programmeren code goed/beter structureren.
In dit artikel een korte historie en kenmerken van Objectgeoriënteerd programmeren, waarna ingegaan wordt op de do's en don't van Objectgeoriënteerd programmeren.
Historie
In den beginne programmeerde men één bestand, waarin de code van boven naar onder achter elkaar werd uitgevoerd. (Daarvoor werkte men trouwens met ponskaarten, maar dat is feitelijk dezelfde architectuur.) Met goto waren sprongen mogelijk (niet in ponskaarten!), waardoor if-then en for-next constructies mogelijk maken.Er werd aanvankelijk geprogrammeerd in machinecode, later in Assembly (1e en 2e generatie talen).
Naarmate programma's groter werden, werd het gebruikelijk met meerdere bestanden te werken. Die konden worden ingevoegd (include). Door het gebruik van functies werd het nog efficienter: je include een functie eenmalig en roept hem daarna tig keer aan.
De leidende programmeer architectuur werd procedureel programmeren genoemd (PP).
Bij de aanroep van het programma wordt een proces doorlopen, die één of meerdere stukken code uitvoert, al dan niet ondergebracht in functies.
Er ontstonden nieuwe programmertalen (3e generatie), die deze architectuur ondersteunden. De bekendste zijn C, Cobol, Basic, Pascal en Fortan.
Begin jaren 90 begon de opmars van Objectgeoriënteerd programmeren (alhoewel al in de jaren 60 ontstaan). In het Engels wordt de architectuur Object-Oriented genoemd, afgekort als OO. Je komt ook de afkorting OOP tegen, ofwel Object-Oriented Programming. In dit artikel wordt de afkorting OOP gehanteerd als het gaat om het programeren in OO en OO als het gaat om de architectuur.
OOP werd gezien als de volgende stap, als opvolging van PP. De reden van de opmars was dat computerprogramma's dermate groot, dat er meer behoefte was ontstaan aan een structurering. De code werd verdeeld in objecten, die min of meer onafhankelijk van elkaar kunnen functioneren. Een object kent niet alleen functies, maar kan ook data vasthouden, in tegenstelling tot functies in PP.
Er kwamen diverse programmeertalen die OO ondersteunden, zoals C++ en Object Pascal (jaren 80 ontstaan). Later in de jaren 90 volgden Java en bracht Php een OO-versie uit. In de de eerste jaren van het nieuwe millenium volgde C#.
Sommige talen ondersteunen zowel procedureel programmeren als objectgeoriënteerd programmeren, bijvoorbeeld C++ en Php. Andere talen zijn pure OO-talen, zoals Java.
Kenmerken
OO heeft een aantal kenmerken:| - | Incapsulation (informatie verbergen) |
| - | Inheritance (overerving) |
| - | Polymorphism (polymorfisme, veelvormigheid) |
Bij Incapsulation is ook nog wel een opmerking te maken: in functies in PP code kun je ook lokale variabelen hanteren. Het verschil is wel dat variabelen in classes van buitenaf benaderbaar kunnen zijn (indien met Public aangeduid).
Een kenmerk die niet in het rijtje is genoemd en wel anders is dan in PP, is de definitie van classes met daarin een verzameling functies. In PP kun je een groep functies in een apart bestand onderbrengen, maar de class als jas heeft wel een voordeel. Je maakt een object op basis van een class en roept de functie met de objectnaam ervoor aan. Wel elegant, wel helder.
Daarnaast, het werken met classes is anders. Je schrijft een class, waarna je een instantie van de class moeten maken: het object. Je kan meer objecten maken van één class.
In PP kun je wel resultaten (=data) van functies opslaan in array's, zodat je meerdere 'instaties' hebt. Maar in objecten zitten ook functies (geërfd van de class), in array's zitten geen aan te roepen functies.
Er is een verschil tussen OOP en het gebruiken van classes. Zo wordt in Php veelal de class PDO gebruikt, om sql-injections op te vangen. In Javascript gebruikt je ook objecten, daar kun je gewoon niet omheen. Daarnaast heb ik zelf ook wel classes van het internet gehaald, die ik vervolgens gebruik. Zoals een class voor generatie van Excelbestanden.
Maar, dat wil niet zeggen dat de gehele code OO is opgezet. Dat is een ander verhaal. En in dit artikel gaat het dus om de gehele opzet van je eigen code, niet om het gebruik van standaard- of gedownloade classes.
Met OO is het niet mogelijk om geheel andere toepassingen te maken dan met PP. Het voordeel van OO ligt op het vlak van structurering. Het is overzichtelijker en daarmee beter onderhoudbaar en is code goed herbruikbaar. Althans, als je er verstandig mee omgaat, zie volgende paragraaf.
Do's en Don't
Een goede start is het halve werk. Bij het ontwikkelen van software is analyse en ontwerp noodzakelijk. Of je een waterval methode gebruikt als SDM of een meer agile methode als Scrum, vooraf nadenken blijft een belangrijke stap. Bij PP verloopt het proces van analyseren en ontwerpen anders dan bij OO.Bij PP worden stroomdiagrammen gebruikt, zoals Flow Charts en DFD's. Het proces leidt tot het ontwikkelen van functies, die het proces uitvoeren. De DFD leidt tot een ERD, waarna de database kan worden ingericht.
Bij OO wordt (doorgaans) UML gebruikt (Unified Modeling Language). UML zelf is geen methode, maar een schrijfwijze. Een methode waarbij UML wordt gebruikt is RUP (Rational Unified Proces). UML biedt een verzameling van structuur- en gedragsdiagrammen.
Er is best veel overlap tussen de diagrammen die in PP worden gebruikt. Er zijn ook een aantal unieke. De belangrijkste en voor ontwikkelaars die gewend zijn aan PP moeilijkste stap, is het definieren van de objecten. Of eigenlijk, het gehele object model. Het is nog te doen om aparte objecten te definieren, maar het logisch en inzichtelijk opbouwen van het hele objectmodel blijft lastig.
Als voorbeeld, een game, het bekende Asteroids. We vliegen in een ruimteschip en hebben last van meteoren, die we kapot willen schieten. Maken we twee classes, 'ruimteschip' en 'meteor'? En we kunnen vanuit het ruimteschip schieten. Die kogels, zijn dat aparte objecten, voorkomend uit de class 'kogel'? Er zijn op de achtergrond ook sterren. En als we vliegen, moeten die natuurlijk bewegen. Aparte class 'ster'? En alles beweegt niet zomaar. We willen eerst een openingsscherm met uitleg en een startknop. Maar, is openingscherm dan een object waarvoor een class moet worden geschreven, class 'openingscherm'? En is de startknop op het openingsscherm dan method in de class 'openingscherm' of beschouwen we de startknop als een apart object, dus schrijven we de class 'startknop'. En daarna, dan loopt het spel. Er zal toch een proces moeten lopen, die elke x miliseconde de beweging van alle voorwerpen realiseert. Class 'game'?
We hanteren OO, dus moeten overerving hanteren: één class 'ruimtevoorwerp', daarna instanties daarvan maken voor 'ruimteschip', 'meteor', etc.? Maar ze bewegen allemaal, dus één class 'bewegen' maken, die gebruikt kan worden door alle objecten die voortkomen uit de class 'ruimtevoorwerp'?
Met OO kun je twee verkeerde kanten opgaan.
De ene kant is het opzetten van classes en die gebruiken als functies. Eigenlijk gewoon PP hanteren, maar gieten in classes i.p.v. functions.
De andere kant is het maken van een wirwar van classes, waarbij de logica voor iemand die jouw code bekijkt niet te achterhalen is zonder je toelichting.
Of, zoals ik op een site las:
A wizard is a kind of player.
A warrior is a kind of player.
A staff is a kind of weapon.
A sword is a kind of weapon.
A player has a weapon.
De belangrijkste les hieruit is dat je de gelaagdheid van classes moet beperken. Door parent op parent op parent te maken, ontstaan veel afhankelijkheden en wordt compileren een langdurige kwestie (en bij interpreter languages dus het interpreteren). Niet best voor performance. Maar belangrijker, in het kader van dit artikel: je mist het doel van OO, het overzichtelijker maken van je code.
Toen ik bovenstaande schreef, kwam ik in de verleiding om met Javascript een game te maken op de OO manier. Alhoewel je in Javascript al snel classes gebruikt (Date, Math) en het aanmaken van objecten ook nog wel redelijk gebruikelijk is, is het aanmaken van een class in Javascript niet gebruikelijk. De optie is ook nog maar vanaf 2015 (in ECMAScript 6) beschikbaar. Ik had in elk geval zelf nog geen classes gemaakt in Javascript, alleen in Php, Java en Python.
Javascript ondersteunt nog niet alles, vergeleken met Php, Java en Pyton. Zo kent Javascript geen private variablen. Maar Inheritance en Polymorphism zijn wel mogelijk.
Ik heb in de game Inheritance toegepast, Polymorphism niet.
Qua object model heb ik gekozen voor een class SpaceObject, met childs Meteor en SpaceShip. Daarnaast heb ik nog een class Player gemaakt. De speler trapt immers het spel af. Alhoewel het onderscheid tussen speler en spaceship wat arbitrair is.
Echter, dan loop ik toch tegen een issue aan. De regel is dat je objecten zoals we die in de werkelijkheid kennen omzetten naar classes. Da's mooi. Maar, wat is een game dan? Dat is iets abstracts, die nauw verwant is met het element tijd. Iets wat in OO niet lijkt te bestaan.
Ach, dan maar pragmatisch: een aparte class Game.
Zo blijft het goed overzichtelijk, vind ik. Eén basisclass met twee childs en nog twee andere classes.
Als het spel wordt geladen, wordt automatisch een nieuwe player aangemaakt.
De gebruiker van het spel start via player.start het spel. In die startfunctie wordt alleen de HTML aangepast, waarna via 'new Game' een game object wordt gemaakt. In de instructor van de class Game wordt via 'new SpaceShip' een spaceship object aangemaakt en de timer gezet naar 'Meteor.create_meteor'. Die laatste zorgt ervoor dat elke x seconden er een meteor wordt gemaakt.
Daarnaast worden timers gezet voor de beweging van de ruimteobjecten (Game.movements) en de resterende tijd (Player.alive).
Zo, het spel is gestart.
P.s.: In de code zijn bovenaan alle variabelen waarmee het spel wordt ingesteld geplaatst. Dit is deels voor het gemak, alles staat lekker bij elkaar (i.p.v. verspreid in functies van classes). Maar een aantal moet daar ook staan, vanwege gebruik in meerdere functies van classes: ze moeten globaal zijn.
Er hadden ook constanten kunnen worden aangemaakt, maar daar zie ik zelf niet zo het nut van in. Soms wil je in code de waarde van variabelen wel aanpassen, soms niet.
Slot
Het spel is nog steeds niet af. Ik bedenk er steeds meer bij. Maar, het ging alleen om het principe, om het objectmodel. Je mag het spel zelf spelen en de code (voor jezelf) verbeteren! Opmerkingen zijn altijd welkom.Speel spel
De code downloaden? Klik dan hier
Meer artikelen hierover lezen? Een paar leuke meer inhoudelijk informatieve:
Willem Nabuurs (Al uit het jaar 1994)
OO Wikipedia Nl
OO Wikipedia En
(Een Wiki site in Nederlands en Engels dus, over hetzelfde, maar toch vullen ze elkaar qua informatie wel aan.)
En sites gericht op Javascript OO:
Mozilla for developers
Dan ook nog een paar sites die ingaan op het OO voortraject:
OO Analyse en Design
Wikipedioa UML
lucidchart UML
Tot slot een paar leuke blogs, over het nut en de gevaren van OOP:
Eric Lippert (met dat rijtje 'A wizard is...)
David Cassel (beschouwing van kritisch OO artikel)
Opmerkingen? Ze zijn van harte welkom: email@johnnyhogenbirk.nl!