1. De software levenscyclus 1. Terminologie Software = executable + alle documentatie (source code, manuals, documenten) Software engineering: Def: Te werk gaan volgens volgende drie punten - Systematisch - Gedisciplineerd - Kwantificeerbaar binnen de ontwikkeling, het gebruik en het onderhoud van software. (het toepassen van engineering op software) En het bestuderen van bovenstaande. Def: Zich richten op het ontwikkelen van software die: - foutenvrij is - voldoet aan de gebruikerseisen - geleverd wordt op tijd en binnen het budget Hacker: Iemand die individueel werkt en zich niet aan de discipline houdt (trial and error/quick and dirty) Goede software: - Functionaliteit: betrouwbaar (gemakkelijk in gebruik, werkt als verwacht, goeie performance, faalt gracieus, veilig) - Levering: op tijd en binnen budget - Onderhoud: kleine verandering in noden -> kleine verandering in code (grootste kost beperkt houden) Silver bullet: - complex: veel complexer - attitude: correctheid niet van levensbelang -> minder geld voor over - flexibel: grote sterkte van software, maar kan ook een kwetsbaarheid zijn bij niet geanticipeerde veranderingen en het zich moeten houden aan hardware. - onzichtbaar: ontwikkelaar moet goed zijn in abstract denken. Spelers: - Klant: individu of organisatie die software nodig heeft en dit bestelt. - Ontwikkelaar: lid van een organisatie die producten ontwikkelt. - Gebruiker: mensen die werken met het product (dikwijls lid van zelfde organisatie als klant, bv niet bij consultatiefirma) Softwareontwikkeling: Alle activiteiten die resulteren in een werkend product (geen onderhoud) Daarna nog 2 fases: werking en onderhoud (kunnen tegelijk gebeuren). COTS (commercial of the shelves) vs custom software: goedkoop <-> duur, algemeen <-> specifiek Interne ontwikkeling vs externe ontwikkeling: binnen zelfde bedrijf of niet. Softwareproces: hoe software wordt geproduceerd (welke methodologieën) Software levenscyclus: de verschillende softwarefases over tijd (deel van proces)
2. Fases 2.1 Requirement (WAT -‐ extern) Wat: Noden en beperkingen van de gebruiker ontdekken. Kenmerk: Functionele (wat moet het doen, wat kun je ermee doen) en nietfunctionele (budget, tijd, hardwarebeperkingen) requirements. Problemen: - Niet functionele zijn moeilijk te controleren, meestal pas na afwerking. - Klant weet niet wat hij wil (welk product? Wat? – niet altijd een nieuw programma) Resultaat: Informeel document, beschrijft noden van klant
2.2 Specifica:on (WAT -‐ extern) Wat: Precies en overvloedig beschrijven van functionaliteit en beperking. (basis voor contracten) Problemen: ambigue zaken en niet precieze zaken, onvolledige specificaties, tegenstrijdige specificaties. Resultaat: Formeel document, specifieert het product.
2.3 Design (HOE -‐ intern) Wat: Bepalen hoe het product de specificaties zal realiseren en voldoen aan de beperkingen. Bepalen hoe het inwendige van het product eruit zal zien: - Architecturaal : compositie van modules - Architecturaal : interactie tussen modules (interfaces, informatievloed) - Gedetailleerd : selecteren van algoritmes en datastructuren voor elke module (inwendige van modules) Resultaat: Architecturaal design, beschrijving van het product als een set van modules en gedetailleerd design, beschrijving van elke module.
2.4 Implementa:e Wat: Alle modules coderen en testen (door de programmeur). Kan in parallel gebeuren door goede documentatie in vorige fases van de cyclus. Resultaat: Gedocumenteerde broncode.
2.5 Integra:e Wat: Alle modules samenvoegen en verifiëren dat het product werkt als geheel. Resultaat: Werkende versie van het programma (einde softwareontwikkeling)
2.6 Onderhoud Wat: Het product levend houden (zolang als economisch leefbaar is) na in werking stellen en end-of-service (drastische veranderingen nodig, te duur om te onderhouden) bepalen. Types: (inclusief updates van de documentatie) - Correctief - Implementeren van omgevingsaanpassingen - Implementeren van verbeteringen Resultaat: Een werkend product
2.7 Re:rement Wat: Stoppen met het gebruiken van het product. Waarom: - kosten onderhoud > ontwikkelen van een nieuw product - Opeenvolgende veranderingen -> instabiliteit, onbetrouwbaarheid - Hardware buiten gebruik Structureel paradigma komt overeen met het object-georiënteerde paradigma: 1. Requirement 2. OO analyse (OOA) = specification fase + architecturaal design a. Functionaliteit bepalen b. Objecten extraheren 3. OO design (OOD) = gedetailleerd design 4. OO programming (OOP) = implementatie 5. Integratie 6. Onderhoud 7. Retirement Meer dan 2/3 van het geld -> onderhoud, dus zeer belangrijk! Ook de relatieve kost om een fout te herstellen stijgt exponentieel per fase, dus belangrijk om zo vroeg mogelijk fout eruit te halen. Beperkte tijd in implementatie door parallel werken. Gebruik van metrieken voor timing, budget, kwaliteit...
3. Processen (= procedure om een systeem te bouwen) Overeenkomst: afgeleverde types van items - Na requirements: requirements specificatie -> belangrijk voor validatie (bouwen we het juiste systeem?) - Na design: technische systeemspecificatie -> opdelen in kleinere delen om de design te herwerken en parallel te kunnen implementeren - Na implementatie: set van programma’s -> gevolgd door validatie en testing - Na testing: testverslag -> daarna aflevering of correctie Types: - Waterval (traditioneel) : sequentieel verloop, lijkt logisch, maar:
o Geen interactie met de gebruiker voor het afgewerkte product -> dure en moeilijke veranderingen o Eerste gebruik is met de volledig afgewerkte versie - Iteratief (modern): proberen opdelen van proces in kleine deeltjes, werken met tussentijdse producten, die stijgen in complexiteit en functionaliteit. Gebruiker wordt hiermee geconfronteerd: o Vroege feedback o Gebruiker kan al beginnen werken met een lichtgewicht versie. Build: eindproduct van een iteratie, werkend, maar zonder volledige functionaliteit Release: een build, afgeleverd aan de klant. Veel gebruik van hybride processen: deel in zijn geheel (meestal design en specificaties) en ander deel in stukken (meestal de implementatie) Problemen: - Ontwerp gebruiken dat extensies toelaat -> abstractie + complexiteit, aan de andere kant maakt dit het ontwerp ook robuster - Ontwerpen van wegwerpmodules - Introductie van fouten in eerder werkende code Oplossingen: - Automatische regressietests - Continue integratie (ondersteuning voor regressietests) o Nieuwe code op de ‘svn’ zetten o Automatisch builden van een nieuwe versie o Automatische run van alle regressietests (mogelijk ’s nachts) - Refactoring: toepassen van automatische transformaties op bestaande code. - Time boxing: in plaats van deadline uit te stellen, minder functionaliteit leveren tegen dezelfde deadline.
3.1 Build-‐and-‐Fix - zeer kleine producten - Onmiddellijke codeerfase en hervormen tot de klant tevreden is. - Onderhoud: terug naar hervormen Voordeel: - Snel (dus goed voor extreem kleine programma’s) Nadeel: - fouten kunnen niet vroeg ontdekt worden -> voldoet niet voor realistische projecten.
3.2 Waterval - Fases in opeenvolging van elkaar, sequentieel - Na iedere fase, specifieke testing - Bij fout: herstarten vorige fase -> fix -> terug gewone sequentie Voordeel: - documentgedrevenheid zorgt voor gereduceerde onderhoudskosten. - Gedisciplineerd Nadeel: - budget en timing problemen bij vaak moeten terugkeren naar vorige fase. - Klant krijgt enkel finale versie van het product te zien (zeer dure aanpassingen) -> Requirement risico’s Kenmerken: - document gedreven: fase pas compleet als documentatie compleet en goedgekeurd is (door SQA). - Documentatie gecontroleerd door SQA (software quality assurance)
3.3 Rapid prototyping -
lijkt sterk op watervalmodel Requirements -> Rapid prototype o Klant zo vroeg mogelijk confronteren met een werkend product (probleem watervalmodel vermijden) Soort requirement vergaringstechniek Gemaakt met build-and-fix (totdat voldoet aan de noden van de klant) Enkel basisfunctionaliteit (eventueel dummy)
Voordeel: - Zelfde als watervalmodel, maar zonder het nadeel dat de klant enkel finale versie te zien krijgt -> kleiner aantal terugkeringen naar een vorige fase. -> verminderde requirement risico’s Nadeel: - prototypes moeten weggegooid worden (anders continue betaversie..)
3.4 Incremental -
Identiek aan twee vorige tot aan architecturaal design Overige fases zijn series iteraties Klant werkt met opeenvolgende versies van tussentijdse builds, deze moeten dus bruikbaar zijn.
Voordeel: - “early return on investment” : klant mag al snel beginnen werken met het programma - Hierdoor worden requirement problemen veel sneller ontdekt. - Onderhoudbaarheid
Nadeel: -
zeer goed verzorgd design en een open architectuur nodig: pluginmogelijkheden anticiperen zonder dat je soms al weet wat ze zullen inhouden.
3.5 Synchronize-‐and-‐Stabilize (MicrosoR) -
Requirements opgedeeld in sets van features (die geïmplementeerd dienen te worden in verschillende builds) Elk team krijgt een build om te construeren, dit gebeurt dan parallel door de verschillende teams. Nadat een build af is, wordt deze bevroren en wordt overgegaan naar stabilisatie. In het eindproduct moeten alle builds goed samenwerken.
Gevaar: - Parallel werken: niet alle builds werken goed samen Oplossing: - Elke dag eindigt met synchronisatiefase waarbij het complete product wordt samengesteld uit de huidige stand van builds. - Problemen moeten nog dezelfde dag aangepakt worden. - Elke dag eindigt zo met een werkende versie van het product. Voordeel: - Verminderde requirement risico’s - Geen integratierisico’s Nadeel: - Enkel voor Microsoft
3.6 Spiraal -
-
Risicogedreven: voor het starten van een fase -> risico’s analyseren! Enkel wanneer de risico’s aannemelijk zijn wordt de fase gestart. Anders, stoppen met de ontwikkeling. Risicoanalyse: analyseren van de risico’s + technieken zoeken om risico’s te beperken. (bv. prototype om begrijpbaarheid te verhogen, alternatieve oplossingen zoeken voor het gegeven probleem, goed gekwalificeerd persoon selecteren, opties zoeken om op terug te vallen in geval van nood) Risico’s: personeel (voldoende + competent), leveranciers (hardware en software op tijd?), subcontractors (tijdige levering? Voldoende kwaliteit?), omgevingsveranderingen (concurrerende producten) , tijd (meer personeel nodig? Snellere afhandeling bepaalde fases?)
Identifieer grootste risico -> los problemen geassocieerd met het risico op! Gezien als “addon-model”, includeren van risicoanalyse.
Voordeel: - Combinatie van voordelen
Nadeel: -
Risico-gedreven Enkel voor projecten binnenshuis Overhead (enkel voor grote projecten) Ervaring nodig in risicoanalyse
3.7 Fontein (OO) - Iteraties zijn kenmerkend voor alle OO-modellen (ook binnen fases) - Overlap tussen verschillende fases. Gevaar: - vervallen in CABTAB (code a bit, test a bit -> hacker)
3.8 Ra:onal Unified Process /Objectory Process (RUP – OO) -
-
Werkende versie na elke iteratie Fases: o Inception: is een nieuw product echt nodig? (marktstudies, alternatieven) o Elaboration: requirements + specificatie (+ risico analyse en planning, definiëren van iteraties) o Construction: construeren van product in een sequentie van builds, gedaan als een complete versie bekomen wordt. o Transition: Alle activiteiten voor levering, geen functionele toevoegingen. (optimalisatie, bugfixes, layouts) Set van processen, getuned op een bepaalde set softwareproducten -> subset kiezen dat het best bij je product past. Eerste stap is een business case selecteren, hierin wordt bepaald welke stappen deel zullen uitmaken van het proces. (standaard cases beschikbaar)
3.9 Agile processes -
Motivatie hoog houden door langdurige processen te versnellen Korte deadlines, veel iteraties, time-boxing
Hoe selecteren? Er is geen standaardoplossing, hangt af van de omstandigheden. (team, klant, project) Een proces kiezen dat een opbouwende ontwikkeling ondersteunt en waaraan veranderingen kunnen worden aangebracht (bv. RUP). Starten met een as-is, out-ofthe-box profiel en na elke iteratie het proces evalueren en aanpassen. - Behoud de features van het proces die OK zijn - Problemen: de procesgerelateerde problemen die je wilt oplossen - Probeer: veranderingen aan het proces implementeren -> project retrospective (project reflection als deel van het proces)
4. Modelling met UML (Unified Modelling Language) Softwareontwikkeling = abstract & onzichtbaar -> visuele voorstelling is handig. Voordelen van een standaardwijze om software voor te stellen: - betere communicatie (tussen teams, tussen klant en ontwikkelaar, binnen een team) - verstaanbaar voor niet-experten - onafhankelijk van implementatietaal (-> hergebruik) Schetsmodus: (informeel!) - Communicatie van ideeën - Redeneren over toekomstige code (forward engineering) - Redeneren over bestaande code (reverse engineering) - Incompleet, eerder begrijpbaar dan volledig! Blauwdrukmodus: (formeel, compleet!) - Voor specificatiediagram - Voor de ‘coders’ - Weergave van moeilijk te vatten delen - Forward engineering: automatische codegeneratie - Geleidelijke overgang van schetsmodus naar blauwdrukmodus Verschillende perspectieven van waaruit UML bekeken/gebruikt kan worden: - Software: structuur, gedrag en requirements weergeven - Conceptueel: probleemdomein modelleren - Requirements (en OOA): case diagrammen, conceptual class diagrammen, activity diagrammen en state diagrammen - OOD (en OOA) : klassendiagrammen, sequentiediagramma, packagediagrammen, state diagrammen en deployment diagrammen.
2. Object georiënteerde Analyse (OOA) 1. Requirements (geen deel van OOA) en specificaHes Drie grote stappen: - Uitkiezen (elicitation) van requirements: hoofddoel -> probleem begrijpen dat opgelost moet worden. Bronnen: gebruikers (veelal interviews) en domein zelf (experts, literatuur..) - Specificaties: beschrijven van de set van requirements om te komen tot een requirementsdocument. Dit document wordt geleverd aan de gebruikers, om als feedback te dienen voor de “elicitation” stap - Validatie (correcte requirements) en verificatie (requirements correct neergeschreven): alle partijen moet het eens zijn over de specificaties Bestaat veelal uit iteraties binnen het proces zelf. Er bestaan ook veel technieken voor elke stap. Doel: het bepalen van de eigenlijke noden en beperkingen van de gebruiker. (Niet altijd wat hij zelf zegt) Kenmerken: Functioneel vs Niet-functioneel Communicatie met de klant zeer belangrijk! (foute requirements -> hoge kosten) Technieken: Interviews: interactie tussen klant en ontwikkelaar tijdens vergaderingen o Gestructureerd (gesloten vragen, ja/nee) -> goed om het ijs te breken, om zaken te weten te komen die algemeen zijn over alle projecten; echter te beperkend om de essentie te weten te komen. o Ongestructureerd (open vragen) o Vragenlijst aan de werknemers -> nadeel: interviewers kunnen geen reactie bieden op antwoorden. o Camera’s om problemen te ontdekken -> bestreden Scenario’s: min of meer formele set van opeenvolgingen van gebruikersacties (en geassocieerde acties ondernomen door het product) Een set “als..dan”-zinnen die de interactie tussen gebruiker en product weergeven. o Lijst van acties en reacties o Screenshots (visueel aantrekkelijker en toegankelijker, maar meer werk om te implementeren) Snelle prototypes: wegwerpproduct met gelimiteerde functionaliteit construeren. - Met build-and-fix model geconstrueerd: verder veranderen tot de klant tevreden is.
- Implementeert een kleine subset van functionaliteit, met als doel extra requirements te vinden en oude requirements up te daten. - Implementering van de meeste functionaliteit die zichtbaar is voor de gebruiker (inputschermen, rapporten, menu’s...) - Klant tevreden -> bevriezen -> requirements fase voorbij - Nadruk op snelheid: enkel bugfixes die nodig zijn om de requirements duidelijk te maken, geen documentatie => kan nooit dienen als specificatiedocument (te formeel voor rapid prototype) - Kan ook nooit dienen als eerste versie door build-and-fix! Best een combinatie van alle voorgaande technieken! Standaarddocument voor requirements (zie hoofdstuk A.2 paragraaf 1.2 blz. 9-10) Testing (SQA): Testen van de requirements zelf o Controleren of alle toekomstige gebruikers zijn gecontacteerd. o Consistentie van de requirements controleren -> bij ontdekken contradicties, contact opnemen met klant en requirements team Ontwerpen van toekomstige tests o Uit de requirements en vooral uit de scenario’s kunnen validatietests worden gehaald om het uiteindelijke product op requirements te testen. Metrieken: Aantal nieuwe/veranderde requirements per tijdseenheid -> gebruikt om het einde van de fase te bepalen, bij een bepaalde ondergrens wordt overgegaan. Aantal nieuwe/veranderde requirements per tijdseenheid NA het voltooien van de requirementsfase -> hoog aantal betekent dat nog een extra inspanning vereist is. Gebruikersinteractielogs: zien welke delen van het product van specifiek belang zijn voor de organisatie van de gebruiker/welke delen problematisch zijn.
2. HoofdacHviteiten en UML Externe van het product bepalen. Bevat stappen om de gewenste mate van formaliteit (in de “specifications fase”) en precisie te bereiken. Daarnaast wordt ook het architecturaal design hier aangepakt in de zin van het bepalen van klassen en objecten van het probleemdomein. (WAT moet het product doen)
Iteratie doorheen volgende stappen (met behulp van UML – visuele modeling): - Formaliseren basisrequirements (d.m.v. interactie tussen gebruiker en ontwikkelaar) => Use-case diagrammen - Identificatie van klassen (welke klassen zijn nodig om de essenties van probleemdomein voor te stellen?) => CRC-modelling - Identificatie van een klassenhiërarchie (wat zijn de relaties tussen de klassen onderling?) => Klassendiagrammen - Verbindingen tussen objecten ontdekken (welke objecten zijn nodig en wat zijn hun onderlinge verbindingen?) => Interactiediagrammen - Modelleren van het gedrag van objecten (hoe veranderen objecten doorheen de tijd?) => State diagrammen
3. Use-‐case modelling Een zicht op de software vanuit het standpunt van de gebruiker. - Functionaliteit zoals gezien door de gebruiker: use-case - Interactie tussen gebruiker en systeem: lijn tussen actor en use-case - Types van gebruikers: actor = Actor + use-cases Doelen: -
Functionele requirements definiëren Duidelijk en eenzijdig beschrijven van de interactie tussen gebruiker en systeem Basis voor validatietesting
Actor: Een gebruiker die interageert met het systeem (een rol gespeeld door een externe gebruiker van het systeem). - één persoon kan meerdere actors voorstellen - hoeven geen fysieke personen te zijn (ook hardware/softwaresystemen) Use-case: Externe benodigde functionaliteit door (een groep) van actors, beschrijft meestal het doel van een actor. (altijd verbonden met minstens één actor) Te vinden met behulp van de scenario’s (implementatie van use-case) en het identificeren van externe gebeurtenissen (systeem antwoordt hierop) Gebruik van <<extends>> (uitbreiding) om een variatie op een standaard use-case uit te drukken. Gebruik van <<uses>> (compositie) wanneer dezelfde functionaliteit hergebruikt wordt (zelfde pijl) => vanaf UML 2.0 <
> Typisch voorzien van scenario’s die de functionaliteit beschrijven aan de hand van een MSS (Main Success Scenario), de typische gang van zaken en aftakkingen daarvan. Één MSS per use-case, elke stap kan gebruik maken van <<extends>> of <<uses>>.
Onder de rubriek extensions dienen de alternatieve scenario’s te komen (op de MSS), telkens aangegeven door het volgnummer van de stap in de MSS en een voorwaarde. Eindigen steeds met een return naar de MSS. Pre(Post)-condities: de voorwaarden die moeten voldaan zijn voor (na) de usecase start (afgelopen is). Triggers: gebeurtenissen die de use-case in gang zetten.
4. Klassen modelling Doel:
-
Essentiële klassen en attributen ontdekken (in deze fase enkel klassen van het probleemdomein!) - Structuur en hiërarchie van klassen ontdekken = Statisch data-georiënteerd Technieken: - Zelfstandige naamwoorden uit een beschrijving halen om klassenkandidaten te vinden - CRC-cards: vinden van basisfunctionaliteit van klassen UML klassendiagrammen: relaties tussen klassen vinden Categorieën om klassen in terug te vinden: externe entiteiten die informatie produceren/consumeren, dingen die deel zijn van het probleemdomein, gebeurtenissen tijdens het werken met het programma, rollen gespeeld door interagerende personen, organisationele eenheden binnen het programma, plaatsen, structuren die objecten agregeren of relaties definiëren binnen objecten... Zelfstandige naamwoorden extraheren: Kleine tekstjes schrijven die het probleem omschrijven, samen met de belangrijkst requirements Isoleer alle zelfstandige naamwoorden Verwijder zelfstandige naamwoorden verwijzend naar niet fysische/abstracte entiteiten of verwijzend naar entiteiten buiten het probleemdomein. ð eerste indicatie voor welke klassen er zullen voorkomen (iteratief proces, dus zal nog veranderen gedurende de loop van het project) CRC-cards: (Class Responsability-Collaboration card) Identificeren van de basisfunctionaliteiten van elke klasse. Naam Type (externe entiteit, rol, gebeurtenis) [Optioneel] Karakteristieken (persistent,..) [Optioneel] Verantwoordelijkheden: welke functionaliteit behoort toe aan welke klasse? -> lijst met methodes en attributen Samenwerkingen: welke andere klassen zijn nodig om deze functionaliteit te voorzien?
Kunnen daarna in een diagram gezet worden waarbij veel samenwerkende klassen samengezet worden. Testen of alle kaarten gemaakt zijn kan door de scenario’s te spelen met een groep. Ook attributen kunnen zowel gehaald worden uit de CRC-cards als uit de beschrijving. (wat is er nodig om een object van een klasse compleet te definiëren?) Exacte type nu echter van geen belang. UML Klassendiagrammen: Statische relaties (blijven geldig gedurende de loop van het programma, bv. uitbreiding) tussen klassen modelleren. Types relaties: Subtypes: relatie tussen types (“is een..”) Associaties: relatie tussen instanties (elke andere relatie) Types notatie: Korte notatie: enkel klassennaam Volledige notatie: naam, attributen en methodes Standaardnotatie attributen: zichtbaarh. naam : type multipliciteit = default {eigenschap} Vb. + auto : String [5] = null {ordered} Zichtbaarheid: publiek '+', protected '#', package '~', private '-' Multipliciteit: exact één '1', één of nul '0..1', nul of meer '*', in [a,b] 'a..b' Eigenschap: {readyOnly}, {ordered},{unordered} -> afgeleide (berekende) eigenschappen voorafgegaan door / Standaardnotatie functies: zichtbaarh. naam (richting naam : type) : return-type {eigenschap} Vb. f (inout c : char) : void {query} Richting: in : input parameter, out: output parameter, inout: beide Eigenschap: {query} : pure getter Associatie: relatie tussen instanties van klassen (een specifiek object A staat in relatie met een specifiek object B), rollen worden gebruikt om de relatie te verduidelijken (aan elk van de uiteinden van de verbinding, een gewone lijn), eventueel met zichtbaarheidsaanduiding. Ook multipliciteit wordt aan de uiteinden aangeduid, te kizen uit 1, 0..1, * en a..b. Navigeerbaarheid duidt aan of een partner van de associatie toegang heeft tot de andere partner (pijl – leeg pijluiteinde - wil zeggen: “heeft toegang tot”, geen pijl = niet gespecifieerd) -> bi-directioneel: gevaar voor inconsistentie (best één klasse bevoegdheden geven om consistentie te verzorgen, liefst de klasse aan de 1-kant) Kunnen omgewisseld worden met attributen, niet duidelijk welke de voorkeur geniet, afhankelijk van duidelijkheid en grootte van types (bv. attribuut voor primitief type).
Aggregatie (lege ruit aan kant eigenaar) : Zwakke “heeft een”-relatie Compositie (volle ruit) : “heeft een”-relatie, delen behoren toe aan één klasse en worden mee vernietigd bij vernietiging eigenaar. Generalisatie en subtypes: (volle lijn, gevuld pijl uiteinde) Als A kan vervangen worden door B, dan is B een subtype van A (B extends A) A is hier een generalisatie van B. Abstracte klasses: Klassennaam in cursief, alsook de abstracte methodes. Eventueel aanvulling van naam met {abstract} bij onduidelijkheid. UML note: vertrekt met stippellijn vanuit bv. een verbinding en eindigt in een rechthoek met omgeplooide flap. Eventueel aangeduid met -- voor een commentaarregel. Interfaces: Aangeduid met <> bij klassennaam, klassennaam cursief, methodes niet. Korte notatie door zelfde als abstract of lollipop (verticale lijn met bolletje bovenop, naam ernaast, niet cursief). Subtypes van interfaces: Streepjeslijn in plaats van volle lijn, behalve bij lollipop, daar blijft een volle lijn staan. Afhankelijkheid: Streepjeslijn met leeg pijluiteinde. (bv. bij gebruik als argument) A is afhankelijk van B, als veranderingen in B zorgen voor veranderingen in A. Enkele (optionele aanduidingen): <> Associatieklasse: Beschrijft een associate (1-1 relatie tussen associatieklassen en associatie, 1 - * duidt dan aan dat één object van type A in * verschillende associaties geassocieerd is met één object van type B) -> 1 * * 1 naar 1 * Beperkinen: genoteerd zoals UML notes, maar dan zonder verbinding. Kan in natuurlijke taal of pseudocode, tussen { en }.
5. InteracHe modelling Doel:
Types:
-
uitzoeken hoe objecten interageren in functie van de tijd Scenario’s (dynamisch) en klassenstructuren (statisch) combineren Afronden deze iteratie van de OOA (use-case modellering -> statische modellering -> dynamische modellering)
-
Sequentiediagrammen: interactie in functie van tijd Collaborationdiagrammen: groepen van intensief samenwerkende objecten State diagrammen: status van objecten in functie van de tijd
Standaardvoorstelling van objecten: in een kadertje, naam object : naam klasse (steeds onderlijnd, klassennaam mag weg, dubbele punt mag ook weg) Sequentiediagrammen: Toont het doorgeven van berichten tussen objecten in functie van de tijd. De creatie van nieuwe objecten wordt getoond door het starten van een nieuwe levenslijn. Berichten kunnen ook afkomstig zijn van de actor. Horizontaal: interagerende objecten Verticaal: verloop van tijd Methodeoproep: aangeduid met een pijl, methodenaam erbovenop (new bij constructor) Return: impliciet! (mag wel aangeduid worden – met stippellijn - maar hoeft zeker niet) Controle-informatie: o conditioneel: voorwaarde boven methodelabel zetten o iteratief: een iteratie wordt aangeduid met * voor de naam Keuze tussen conditioneel of bijkomende scenario’s -> conditioneel! Activatieblokken (optioneel): welke objecten zijn op dit moment actief? > minstens één methode op callstack (leesbaarheid + duidelijke call-backs) Found message: methodeoproep vanuit een niet getoond object (om te beginnen) Vernietiging: impliciet: kruis door levenslijn, zonder pijl, expliciet: met pijl (wijzend naar kruis) Synchrone oproep: volle pijl (vroeger lege pijl) Asynchrone oproep: lege pijl (vroeger halve pijl) Instructievensters (gekoppeld aan sequentiediagrammen): Doel: meer gestructureerde mogelijkheden om controlelogica te binnen sequentiediagrammen te tonen. (sequentiediagrammen echter niet de beste keuze, beter pseudecode) Kunnen genest worden. Guard: voorwaarde Operators: alt (alternatief, enkel de optie waar de guard waar is wordt uitgevoerd), opt (enkel als guard waar is), par (parallel), loop (while -> rekening houden met guard), region (kritieke regio, 1 thread max.), neg (ongeldige interactie), ref (referentie naar ander interactiediagram – eventueel met parameters en return val), sd (rond volledig sequentiediagram, geen betekenis). Collaboration diagram (Communicatiediagram): essentie is hetzelfde als sequentiediagram, hier geeft men echter het duidelijke verloop van de tijd op, in ruil voor vrijheid van ordening over de 2D-ruimte. Alle methodes genummerd (eventueel hiërarchisch), ook ruimte voor condities en iteraties.
6. Modelleren van het interne van objecten Met behulp van state diagrammen. Doel: de evolutie van objecten gedurende de uitvoering volgen. Bestaat uit 2 elementen: - De status van het object (“do/”activiteit) -> kan onderbroken worden door andere activiteiten in een multithreaded omgeving - Overgangen tussen statussen (“Event”[voorwaarde]”/”Actie) -> alledrie optioneel: een overgang vindt plaats als de gebeurtenis plaatsheeft en aan de voorwaarde voldaan is. Tijdens de transactie zal de actie ook uitgevoerd worden, deze kan niet onderbroken worden, aangezien overgangen onmiddellijk gebeuren.
7. Tests en metrieken OOA bestaat in essentie uit 3 delen: 1. Use-case: extern gebruik vatten 2. Statisch modelleren: klassen en hun onderlinge relaties identificeren 3. Dynamisch modelleren: relaties en interacties tussen objecten in functie van de tijd en evolutie van de staat van object in verloop van de tijd. Bij stabilisatie kan het uiteindelijke specificatiedocument worden geschreven: bevat alle gemaakte diagrammen, samen met een duidelijke en eenzijdige beschrijving. Testing: Niet uitvoerings-gebaseerd (want nog geen code om mee te werken) Basisidee: experts buiten het OOA-team, het document laten controleren. 1. Walktrough: - 4-6 personen: klant, specificatieteam vertegenwoordiger, specificatiemanager, design team vertegenwoordiger, SQA vertegenwoordiger. - Enkel identificatie, geen correctie! - Iedereen maakt 2 lijstjes voor de meeting: dingen die hij niet begrijpt en dingen die hij incorrect acht. Tijdens de meeting overlopen. 2 manieren: documentgedreven (specificatiedocument overlopen en per paragraaf de problemen bespreken) en lijstgedreven (iedereen zijn lijstje overlopen). 2. Inspection: - team van 4: moderator/designer/iem. die implementeert/tester (SQA) - beschikken over een lijstje met mogelijke fouten (uit ervaring, literatuur, gekende problemen bij dat type programma..)
- 5 stappen: 1. Overzicht: een teamlid stelt een overzicht van het specificatiedocument voor zodat het team een overzichtsbeeld krijgt van het product onder constructie. 2. Voorbereiding: de teamleden lezen, discussiëren en interageren om het document te begrijpen. 3. Inspectie: focus discussie -> fouten vinden, niet opgeloste problemen van tijdens de voorbereiding worden aangepakt. 4. Herwerken: specificatieteam krijgt feedback en past het document aan. 5. Opvolging: Opvolging bestaat erin te controleren of alle hints/problemen in rekening werden gebracht bij het updaten en dat er zich geen nieuwe fouten hebben geïntroduceerd. - Bij meer dan 5% fouten -> opnieuw starten - Bijhouden van tegengekomen fouten (aantal, zwaarte, type) 3. Correctheid bewijzen: - wiskundige kennis voor nodig, tijdrovend en gecompliceerd - voor moeilijk te begrijpen/te testen delen van het programma, zelden voor een volledig programma. Metrieken: aan de hand hiervan de “fundamental unknowns” proberen te schatten - grootte - kwaliteit - kost - duur - inspanning twee soorten metrieken: - grootte gerelateerde: aantal pagina’s in specificatiedocument, aantal klassen, aantal methodes in klassen - kwaliteit gerelateerde: foutstatistieken -> gerelateerd aan bedrijf Opletten voor analysis paralysis, het blijven steken in documentatiefases.
3.
Object georiënteerd Design (OOD)
1. Intro HOE? Impliceert een verandering in focus, maar toch streven naar een zo groot mogelijk behoud. Toch, ander doel -> andere diagrammen. Maken van abstractie van de code -> meer hergebruik van design! Doel: 1. Systeemdesign - Architectuur vanuit een implementatiestandpunt bekijken - Conceptuele architectuur -> architectuur voor implementatie 2. Objectdesign - complete beschrijving van objecten vinden (welke datastructuren en algoritmes ?) - interactie tussen objecten beschrijven (welke interfaces en methodeheaders ? Op welke manier gebeurt de interactie?)
2. Systeemdesign Toch nog iteratie mogelijk tussen OOA en OOD, want OOD is specifieker, waardoor nog bijkomende ambiguïteiten en onduidelijkheden kunnen optreden! Stappen: 1. Deel het analysemodel op in verschillende subsystemen 2. Zoek concurrency en alloceer subsystemen. 3. Design user interface 4. Design data management 5. Design taak management 6. Bronnen management 7. Communicatie tussen de subsystemen
2.1 Opdelen in subsystemen Doel: subsystemen identificeren Werkwijze: Op basis van het analysemodel hebben we al de een set van klassen uit het probleemdomein, samen met hun interacties en relaties. Op basis daarvan subsystemen extraheren, deze hebben volgende eigenschappen: - Sterk verbonden (veel interactie, sterke relaties: subtypes, associaties) - Communicatie tussen subset en de “buitenwereld” beperkt tot enkele (of één) klasse in de subset -> communicatieklassen Hierna kan design gedaan worden op subsysteem-niveau (verschillende teams, elk één subsysteem)
Eigenschappen: - Gekarakteriseerd door zijn functionaliteit (services geleverd aan de buitenwereld) en zijn interface die deze functionaliteit implementeert (in de communicatieklasse) - Om het aantal subsystemen laag te houden en de mogelijkheid te hebben het systeem te beschrijven op verschillende detaillevels -> organisatie subsystemen in een hiërarchie: lager niveau -> specifieker, hoger niveau -> som lagere niveaus. Communicatie tussen subsystemen: - Client-server: subsysteem A levert een dienst aan subsysteem B - Peer-to-peer: subsystemen A en B leveren elkaar diensten Architecturale stijl – layering: Manier om de hiërarchie te presenteren, verschillende lagen van abstractie. Hoe hoger, hoe meer zichtbaar voor de eindgebruiker. Lagen dienen onafhankelijk te zijn en zo weinig mogelijk overlap te hebben (=layer violation). Three tier business architectuur : - presentatielaag: user-interface voor de eindgebruiker - business logica laag: business logica van de applicatie - persistente data laag (databank): toelaten van persistente opslag van data Four tier architecture: idem, maar met data formatting laag net boven de persistentielaag. (deze formateert de data op een bepaalde manier, bijvoorbeeld conversie naar euro)
2.2 Concurrency: alloca:e van subsystemen Doel: identificeren van verschillende (gelijktijdig actieve) taken binnen het systeem. Werkwijze: de dynamische analysemodellen zouden dit moeten weergeven (vooral de sequentiediagrammen!) Meerdere gelijktijdig actieve taken? Indien niet -> single-threaded, Indien wel -> multi-threaded -> uitvoeren op één of meerdere processors? Één of meerdere machines? (performance (meer processors->sneller), kost (van hardware), overhead (meer processors dan optimaa) om te bepalen)
2.3 User interface Doel: gebruiksvriendelijke toegang tot systeem bieden Werkwijze: starten van de scenario’s en use-cases uit de analyse -> voor elk scenario de sequentie van operaties identificeren -> opstellen commandohiërarchie (vaak gebruikt hoog, gespecialiseerd, laag). Steeds updaten tot alle scenario’s/use-cases overlopen zijn. Hoog -> menubalk/toolpalet Laag -> hiërarchisch gesorteerd in menu’s
2.4 data management Doel: Beheren van data essentieel voor de applicatie, bewaren/ophalen van objecten. Veel gebruik gemaakt van relationele databanken. Strategie: - data en methodes voor elke klasse die bewaren van persistente data vraagt - Voor elke klasse een serverklasse die bewaren/ophalen ondersteunt. -> verkieslijk! (lossere koppeling tussen te bewaren object en DBMS) Vb. Een methode storeInDB() in je klasse die ServerClass.storeInDB() oproept (laat gemakkelijk switchen tussen opslagwijzen toe, enkel serverklasse aanpassen)
2.5 taak management Doel: Taken coördineren (enkel bij concurrency) Werkwijze: (voor elke taak) - Beschrijven van karakteristieken: hoe start de taak, wat is de prioriteit, hoe kritisch is de taak? - Identificeer de coördinerende taak: verantwoordelijk voor stoppen van de taak, pauzeren van de taak voor meer kritische/dringendere taken. - Attributen/functies toevoegen om coördinatie toe te laten (prioriteitsinfo, taakvooruitgang..)
2.6 bronnen management Doel: Oplossen van conflicten in verband met gebruik van bronnen (bij concurrency) Taken: oplossen van conflicten op een doordachte manier Er zijn 2 soorten bronnen: - Externe: systeembronnen zoals processorkracht, printen.. Meestal al door besturingssysteem! - Abstracties: Objecten van producten (probleem als 2 threads tegelijk toegang willen). Databasetoegang wordt geregeld door DMBS! Mogelijke strategie: gebruik van een guardian-object per bron (iedereen moet daarlangs passeren, dus bezit alle mogelijk informatie)
2.7 communica:e tussen subsystemen Doel: formaliseren van samenwerking tussen subsystemen Werkwijze: set van contracten per subsysteem, waarin de mogelijke requests worden weergegeven. vb. Contractnaam/type(peer-to-peer of client-server)/collaborators(tussen welke subsystemen)/klassen(nodige klassen voor de gevraagde service)/functies (welke operaties van de klassen)/Berichtformaat (welke argumenten voor de functies)
2.8 Package diagram UML om het complete systeem als set van subsystemen (packages) voor te stellen. Pijlen gebruikt om afhankelijkheden (afhankelijk van publiek lid van een publieke
klasse) te modelleren (verandering in B impliceert verandering in A) => zo weinig mogelijk! Ook package diagrammen kunnen hiërarchisch voorgesteld worden. {global} om voor te stellen dat alle packages afhankelijk zijn van deze package. Ook mogelijkheid voor generalisatie (subtypes moeten minstens een implementatie bieden voor de publieke leden van de publieke klassen van de bovenklasse). Ook abstract kan gebruikt worden in de vorm van {abstract} als eigenschap bij een package (in dat geval is een implementatie vereist in de subtypes! Geen default in bovenklasse) Opnieuw afhankelijkheden vermijden!
2.9 Deployment diagram Doel: relatie tussen hardware en software weergeven, vooral belangrijk in gedistribueerde systemen. Toont de hardware (sensor tot computer) waar een component/object op runt. Nodes: rekeneenheden (soort 3D rechthoek/balk) Verbinding: fysieke communicatiepaden (bv. TCP/IP), gelabeld met gebruikte protocol. Componenten (rechthoek met 2 balkjes links): soort packages -> fysieke modules van code. - korte notatie: enkel naam - + bevatte klassen - + geleverde interfaces (waarmee de component bereikbaar is, communicatieklasse) -> lollipopaanhangsel aan de component Afhankelijkheden (stippellijn tussen componenten itt verbindingen tussen nodes) : logische verbindingen i.p.v. fysieke verbindingen (Node bevat componenten bevat klassen)
3. Objectdesign Doel: Focus kan nu gericht worden op één subsysteem (parallel) - specifiëren van attribuuttypes -> datastructuren (gegeven attributen uit OOA en OOD) - specifiëren van functioneren van methodes -> algoritmes (gegeven verantwoordelijkheden van klassen uit OOA en OOD) - specifiëren van verbindingen/communicatie tussen objecten (gegeven de interfaces uit OOD) Basering op OOA en systeem design van OOD.
Objectbeschrijving: 2 niveaus: - protocolbeschrijving (interface van het object, vanuit standpunt service user, lijst van extern toegankelijke berichten) - implementatiebeschrijving (hoe is de bovenvermelde functionaliteit intern verwezenlijkt? Verborgen houden voor gebruiker om niet te verwarren en updates niet in gevaar te brengen -> gebruiker zou zich teveel gaan baseren op interne werking). Vanuit het standpunt van de service supplier. Algoritmes en datastructuren: - belangrijk beide samen aan te pakken, want sterke koppeling! - Grote beschikbaarheid van oplossingen voor standaardproblemen. (datamanipulatie, computationele operaties, monitoring) - Probleemspecifiek - Klassendiagram voor hints over datastructuren
4. Testen en metrieken Doel:
-
specifacties compleet en accuraat in het design opgenomen correctheid van design: logische/algoritmische fouten, interfacing van object/subsysteem errors (berichtformaat) Technieken: Gelijklopend met OOA: inspection en walktrough, maar zonder vertegenwoordiger van de klant (heeft zijn requirements duidelijk gemaakt, is niet nodig voor interne logica) Fundamental unknowns: grootte, kost, duur, inspanning en kwaliteit op basis van: - aantal modules - cohesie en koppeling van modules - foutenstatistieken - Cyclomatische complexiteit : (#binaire beslissingen + 1) - CMD (coupling dependency metric) : niveau van afhankelijkheid tussen twee modules Voorspelling op basis van eerder cijfermateriaal binnen het bedrijf. CMD is som van drie componenten: - referentiële afhankelijkheid: in hoeverre vertrouwt het programma op het niet veranderen van declaraties? - structurele afhankelijkheid: in hoeverre vertrouwt het programma op het niet veranderen van de interne organisatie? - data integriteit afhankelijkheid: in hoeverre kunnen data-elementen in één module veranderd worden door een andere module
5. Design patronen Goed ontwerp moeilijk doordat het uitbreidbaar en onderhoudbaar moet zijn. (moeilijk vooraf al in te werken) => bundelen van abstracte oplossingen voor veel voorkomende problemen, dit zijn de zogenoemde design patterns. Patroon beschrijving: naam, probleem, oplossing, gevolgen Classificatie: - Creationeel: oplossen problemen in verband met het construeren van objecten (constructor problemen) - Structureel: oplossen van problemen in verband met het opstellen van klassen en objecten, de klassenhiërarchie - Behavioral: Hoe verantwoordelijkheid verdelen en objecten laten samenwerken? Veel gebruikte technieken : overerving (white-box hergebruik, meer dan enkel publieke interface) en compositie/aggregatie (black-box hergebruik, enkel publieke interface) De voorbeelden staan tussen de andere design patters beschreven in het betreffende hoofdstuk.
4. Object georiënteerd Programmeren (OOP) 1. ImplementaHe en integraHe Keuze programmeertaal: drie grote selectiecriteria - overeenkomst met noden van de klant (bv. voor bestaande hardware/ legacy software) - strategie van de organisatie van de ontwikkelaar (aanwezigheid van huidige expertise, verandern is duur -> opleiding personeel, investeren in tools) - applicatiegebaseerde argumenten (generisch <-> specifiek -> kan ontwikkeltijd gevoelig korter maken) AI -> LISP hoge performance voor numerieke bewerkingen -> C, Fortran netwerk management -> java, c++ administratieve software -> java, c++ Codeerstandaarden: moeten automatisch gecontroleerd worden, houdt een compromis in tussen gemak van ontwikkeling en efficiënt onderhoud. Werken in team : conventies -> consistentie voor complete systeem. Voordelen: - teamwork gaat makkelijker - maintenance heeft er baat bij - Leesbaarheid en duidelijkheid Nadelen: - Beperkt de codeervrijheid Vb. Methodes korter dan 35 lijnen, niet meer dan 3 geneste if-lussen, geen goto.. Afhankelijkheden van modules: welke module eerst coderen? - Naïef: alles coderen in volgorde van voorkomen -> integreren -> testen o Problemen: geen foutenisolatie -> vertraging implementatie, je weet niet waar de fout zit (want je test pas als alles af is) o Oplossing: elke moodule/klasse afzonderlijk testen o Probleem: § Klassen waarvan de te testen klasse afhankelijk is nog niet geprogrammeerd -> stubs nodig (dummy klassen zodat de te testen klasse kan compileren) § Een klasse afhankelijk van de te testen klasse is nog niet geprogrammeerd -> driver nodig (simuleert werking van de echte klasse die later geïmplementeerd moet worden) Stubs en drivers: Stubs en drivers enkel voor compilatie en testing! Wegwerpen! Intelligente stubs: print als de methode wordt opgeroepen, geeft resultaten uit test cases
Intelligente drivers: te testen klasse bevat eigen driver in de vorm van een mainmethode -> test cases verwerken indien mogelijk Strategieën: Hoog in de hiërarchie bevat de logica, laag zijn de componenten geschikt voor hergebruik. - Top-down: snel logische fouten ontdekken, geen drivers nodig, maar modules geschikt voor hergebruik het minst getest. - Bottom-up: laat logische fouten ontdekken, geen stubs nodig en modules geschikt voor hergebruik zeer vaak getest. - Sandwich: combinatie van beide voordelen, snel logische fouten en modules geschikt voor hergebruik grondig getest, maar zowel stubs, als drivers nodig. Logische modules top-down (probleem-speifiek, geen hergebruik), operationele methodes bottom-up (ontworpen voor een specifieke taak, veel hergebruik) => compromis
2. Testen 2 soorten: - informeel testen: door programmeerteam zelf, voor hun subsysteem, zodat het correct werkt. - Methodisch testen: door SQA om ervoor te zorgen dat men (binnen het redelijke) zeker is dat het product voldoet aan de noden/beperkingen van de klant. Testtechnieken (methodisch): - niet-executie gebaseerd testen: walktroughs of inspections (in een meeting met experten) - executie gebaseerd testen: test cases identificeren en programma uitvoeren, gegeven de input die overeenkomt met de testcases -> niet gemakkelijk, meestal zeer veel mogelijke inputs -> extreem hoog aantal testcases -> technieken nodig! Testtechnieken (executie gebaseerd): black-box testen: - testen op specificaties (code negeren, enkel baseren op specificatie document), op input/output - Na het informeel testen door programmeurs. - Probleem: combinatorisch aantal testcases Technieken: - elke test moet mogelijk een nieuwe error ontdekken, niet dubbel checken - Elke keer de code aangepast wordt -> opnieuw uitvoeren van testcases (regression testing) - Equivalence and boundary analysis: o Input opdelen in equivalentieklassen (zelfde gedrag binnen een klasse, dus bijvoorbeeld kleiner dan nul en groter dan nul) o 1 testcase (minstens) per klasse o testcase voor de randen van elke klasse (dus op nul in dit geval) white-box (glass box) testen:
-
testen op code, elk mogelijk pad doorheen de module testen combinatorische stijging van aantal tests (lus bestaande uit x mogelijke paden met “n” iteraties -> x^n tests) - toont niet alle problemen Technieken: (gebruik van CASE-tools om de coverage te controleren) - statement coverage: elke instructie moet minstens 1 keer uitgevoerd zijn - branch coverage: elke tak moet minstens 1 keer genomen worden - path coverage: gesofisticeerde methodes controlegraaf testen: - knopen: de instructies (of groepen ervan, zolang er geen controleovergang tussen zit, altijd in sequentie) - takken: controle-overgangen tussen instructies - één component per thread (anders fout in programma) lineair onafhankelijke paden: associatie met vectoren, 1 component van de vector komt overeen met 1 tak in de graaf, getal = aantal keren dat die tak wordt genomen. 2 lineair onafhankelijke vectoren? -> 2 lineair onafhankelijke paden! Grootte van een basis B voor een set van paden = aantal takken - aantal knopen + aantal sterk verbonden componenten (= aantal componenten + 1 -> om begin en einde te verbinden, zodat de componenten sterk verbonden worden (alle knopen bereikbaar door een pad te volgen vanuit gelijk welke andere knoop)) = lineaire complexiteit Technieken: - path coverage : alle mogelijke paden in controlegraaf (lus demonteren) - statement coverage: alle knopen minstens één keer bezoeken - branch coverage: alle takken minstens één keer bezoeken (impliceert statement coverage) - cyclomatic coverage: een basis van paden selecteren - multiple condition coverage: (uitbreiding van branch coverage) alle mogelijke combinaties van condities minstens één keer nemen systeem testen: Nadat alle subsystemen zijn getest en succesvol zijn geï -> test op specificaties voor het hele product (overeenkomst met gewenste functionaliteit en opgelegde beperkingen) - functionaliteit testen: validatietests uit de requirementfase (uit de scenario’s) gebruiken - voor niet-functionele beperkingen -> gespecialiseerde metingen COTS - vroege versies sturen naar mogelijke klanten: alpha en beta versies -> input -> updaten! - commerciële organisaties nemen hieraan deel om een competetieve voorsprong te verwerven (kunnen al eerder wennen aan het product) en om hun eigen zegje te hebben in de functionaliteit. Custom software: na systeem testen door SQA -> acceptance testen (door klant, klant en SQA of door onafhankelijke SQA) Er wordt gekeken naar: - Correctheid : de correcte functionaliteit?
-
Prestatie: de verwachte performance? Robuustheid: werkt het onder stresssituaties? (overleeft het stroomuitval? Wat gebeurt dan met de files?) wat als er 100 mensen inloggen op het systeem? - Documentatie: is alle documentatie geleverd en voldoet het aan de standaarden? Na acceptatie => einde ontwikkeling!
3. Metrieken Vooral voor inschatten complexiteit, zodat meer mankracht kan ingezet worden om deze bepaalde complexe modules grondiger te testen. Aan de hand van : - aantal lijnen code: KLOC (1000 lijnen code) -> in assembler - cyclomatische complexiteit: aantal vertakkingen in de module - 4-tuple (aantal verschillende variabelen, aantal verschillende operators (bv. numerieke operators, methodeoproepen..), totaal aantal variabelen, totaal aantal operators) => allemaal gelijkwaardig (zelfde voorspellingskracht) foutenstatistieken: bevat: - aantal testcases - aantal gefaalde testcases => als (aantal gefaalde/totaal aantal) een bepaalde limiet overschreid -> design opnieuw maken/hercoderen Hoe lang testen? (veronderstelling dat de waarschijnlijkheid op het vinden van fouten exponentieel daalt naargelang de foutenvrij testperiode groter wordt) f_goal = hoeveel fouten als doel? f_total = totaal aantal tot hiertoe ontdekte fouten t_h = tijd sinds laatste fout => testen voor = ( ln (f_goal/(0.5+f_goal) * t_h ) / ln((0.5+f_goal)/(f_total+f_goal)) uren
4. Unit testen (JUnit) -
veel gebruikt binnen eXtreme Programming (coderen en testen gebeurt er namelijk door elkaar. - 2 soorten fouten: o failure: een resultaat is gegeven, maar het verkeerde (of een verwachte exceptie niet geworpen) o error: een onverwachte exceptie werd opgeworpen Een testsuite verpakt een aantal testcases (en eventueel andere suites). Elke testcase bevat één of meerdere oproepen naar de statische assert-methodes uit de Assert klasse: assertEquals (objecten en primitieve types)/assertTrue (booleans) Statisch:
1. testcases definiëren: klasse per testcase (extends TestCase), code in overschreven methode public void runTest() 2. testcases toevoegen aan suites (statisch of dynamisch -> automatische methodes) : moet in een publieke klasse (extends TestCase), code in overschreven methode public static Test suite() -> TestSuite object aanmaken, .addTest erop uitvoeren, object retourneren 3. testrunner laten lopen (textueel, awt of swing – GUI) -> met behulp van java junit.textui.TestRunner Fixtures: tests runnen op een vaste set objecten (setupcode vermijden) - de tests erven nu over van een fixture object - dit object bevat 2 methodes: public void setUp() en tearDown() - fixture zelf erft over van TestCase Eventueel gebruik maken van anonieme binnenklassen voor testcases: - constructor in fixture (roept super op) - String meegeven om test te identificeren Één klasse Dynamisch: - Met reflection: elke testcase in een methode en naam van methode per String opgeven bij het construeren van de suite. - Nog compacter door het gebruik van standaardnaamgeving (elke test moet beginnen met test, classobject meegegeven aan de constructor van de testsuite. - Met annotaties, geen naming conventies meer nodig. o @Test voor de testmethodes o @Before voor setupmethode o @After voor breakdownmethode o Testen op exceptions met @Test(expected=Exception.class) o suite() moet junit.framework.Test JUnit4TestAdapter returnen indien met testrunner, anders geen suite() methode nodig, maar org.junit.runner.JUnitCore.runClasses(class) gebruiken. o @BeforeClass: één keer voor opbouw suite o @AfterClass: één keer na afloop suite
5. DocumentaHe (javadoc) Hele ontwikkelingsproces is documentgebaseerd : specificatie (basis voor contract), basis voor testen (walktrough, inspection), basis voor volgende fase, basis voor onderhoud.. => niet onlogisch de code ook te documenteren
Waarom? - code legt zichzelf nooit uit - verduidelijking: verantwoordelijkheden van een klasse, pre- en postcondities van een methode (zijn doel), onduidelijke programmalijnen verhelderen javadoc: - documentatie in source -> gemakkelijk te onderhouden -> automatische documentatie-extractie - syntax : /** documentatie */ - enkel voor klassen-, methodes- en variabelendefinities - mogelijkheid ingebedde html te gebruiken en document tags - javadoc klasse.java - in browser bekeken - by default enkel publieke en protected elementen javadoc –private klasse.java/javadoc –package klasse.java om ook package zichtbaarheid of private zichtbaarheid te documenteren - @see klassennaam/klassenpad (volledig bv. System.out.println..)/of met #methode om een bepaalde methode aan te duiden -> kan overal staan waar javadoc kan staan - @version (van de software) /@autho (door wie is code geschreven?)/@since (eerste versie van software waarin deze klasse voorkwam) enkel in de klassendocumentatie ook in HTML-versie door “javadoc –author –version klasse.java”
5. Design patronen = gekende oplossingen voor gekende problemen 3 doelen: - creational: creatie van objecten abstraheren - structural: hoe klassen/objecten bouwen, met behulp van andere klassen/objecten om bepaalde designproblemen op te lossen - Behavioral: interactie tussen objecten en verantwoordelijkheden verdelen over samenwerkende klassen en objecten Bereik: - Klasse: relatie tussen klassen en subklassen (focus op overerving). Statische relatie, bij compilen tot stand gebracht. Factory methode, adapter, template (en interpreter) - Object: relaties tussen objecten, kunnen dynamisch aangepast worden, at run-time.
1. CreaHonal (= creaHe van objecten) Doel:
- abstracte objectcreatie - vermijden van hardgecodeerde types in clientcode Probleem: “zwakheid van constructors” - niet overerfbaar -> geen polymorfisme om voor abstractie te zorgen - vast returntype (dat aan de andere kant juist dynamisch is) Oplossing: wrappermethodes voorzien - klasse scope : overerving om het run-time type te bepalen - object scope : creatie delegeren naar object (run-time types afhankelijk van objecttype en gedrag) We willen een object aanmaken aan de hand van een overkoepelend (abstract) type, maar moeten om het aan te maken een specifiek type opgeven => ongewilde afhankelijkheden!
1.1 Factory methode
Oplossing: een constructor-like (wrapper)methode (de factory methode) => op deze manier toch polymorfisme op de constructor toepassen Algemeen: een creator die enkel afhankelijk is van de bovenste klasse in de producthiërarchie. Deze wordt uitgebreid door concretecreators, die staan in verbinding met de concreteproducts. Toepasbaarheid: - bij moeilijk te anticiperen welk type objecten nodig zullen zijn - Wanneer subclassing gebruikt kan worden om deze types te specifiëren Gevolgen: - laat toe het gedrag van de applicatie te veranderen terwijl het grootste deel van de code behouden blijft door middel van subclassing. - laat toe parallelle hiërarchieën te verbinden Vaak gebruikt in andere creational patterns en in b.v. templates
1.2 Abstract Factory Oplossing: Een factory-interface wordt aangesproken door de cliënt om zich te voorzien van objecten (precieze subtype wordt bepaald door de factory), specifieke factory’s implementeren dan deze factory interface en zijn verantwoordelijk voor de aanmaak van specifieke objecten. Toepasbaarheid: - Wanneer onafhankelijkheid van creatie, samenstelling en weergave van een product gewenst is. - Wanneer een systeem met verschillende productfamilies ontwikkeld moet worden. - Wanneer men wil opleggen dat enkel producten uit één familie gebruikt mogen worden. - Ingewikkeldere initialisatie dan enkel de oproep van een constructor. Gevolgen: - concrete producten afgeschermd van applicatiecode - gemakkelijk switchen tussen productfamilies en forceren van consistentie - moeilijk om nieuwe producten toe te voegen -> verandering in interface van abstractfactory -> alle implementerende producten moeten zich aanpassen.
1.3 Prototype Oplossing: Enkele objecten ontvangen at run-time, deze kunnen dan zichzelf reproduceren (om er zoveel te krijgen als je wil) met behulp van de clone() methode. Prototype moet specifiëren dat de clone() methode geïmplementeerd moet worden door concrete prototypes. In java: overkoepelend type “Object” specifieert clone() methode, die een Object retourneert => clonen en downcasten naar werkelijke type. Toepasbaarheid: - at run-time specificatie van de klassen (bv. dynamisch ladend programma) => zeer flexibel (elk object die clone() implementeert kan gebruikt worden)
-
vermijden van de factory-hiërarchie (moet statisch geïmplementeerd worden -> verminderde flexibiliteit) - Te betalen prijs voor flexibiliteit is de grote manuele inspanning van alle gekloonde objecten achteraf nog eens goed te gaan instellen. - als niet veel variatie op de statusinfo nodig is, dan kan een set prototypes bijgehouden worden, één per voor elke mogelijke configuratie => manuele inspanning weg Implementatie: - Diepe kopie maken voor de veiligheid - Prototypemanager voor alle mogelijke prototypes - Factory object kan geconfigureerd worden met behulp van prototypes
1.4 Builder Oplossing: gelijkaardig aan abstractfactory, maar verdeling van de functionaliteit. Voor zeer complexe creatiepatronen van objecten (in een construct() methode), om te hergebruiken doorheen verschillende cliëntapplicaties. Cliënt ontvangt een director, deze weet hoe verschillende delen ineen passen. Hij weet echter niet hoe ze te bouwen, dat doet de builder. Effecten: - Director kan hergebruikt worden voor vele cliëntapplicaties. - Cliënt kan een totaal ander product verkrijgen door andere builder mee te geven aan de director. Director: construct() Builder : buildPart() ConcreteBuilder: buildPart() en getResult() -> geeft Product terug, cliëntcode is hiervan afhankelijk! Toepasbaarheid: - algoritme voor het creëren van een complex object is onafhankelijk van de delen waaruit het bestaat. (typische toepassing: parsers) - verschillende representaties mogelijk van hetzelfde object (terwijl hetzelfde productieproces gebruikt wordt) Meest gebruikt voor parsers (xml), formatting (verschillende fysieke voorstellingen voor eenzelfde object) en compilers.
1.5 Singleton Probleem: Hoe ervoor zorgen dat enkel 1 object van een klasse kan geïnitialiseerd worden? Oplossing: - Constructor private maken - Zorgen dat er geen default constructor is - Singleton verantwoordelijk voor unieke instantie (getUniqueElement() voorzien om uniek element terug te geven), bewaard als static. - Eventueel methodes om de unieke instantie te manipuleren (setters en getters)
2. Structural (=klassenhiërarchie)
Biedt oplossingen voor relaties tussen klassen, hoe klassenhiërarchieën te organiseren en hoe klassen met aggregatie en compositie te gebruiken om hoge kwaliteitsdesigns te krijgen voor een bepaalde sitiuatie. Wrappers: verpakken een ander object (adapter, decorator en proxy) Veranderen van interface: - Adapter wil hiermee de interface van het onderliggend object veranderen om in overeenstemming te zijn met een gegeven hiërarchie. (Grotendeels) behouden van interface: (verbergen van inpakking) - Decorator wil hiermee features toevoegen aan objecten zonder hiervoor overerving te moeten gebruiken. - Proxy wil hiermee gecontroleerde toegang tot objecten voorzien. Vier andere hebben een specifiek doel.
2.1 Adapter Probleemstelling: Niet overeenkomende interface van een klasse die hergebruikt wordt => adapter nodig (biedt twee interfaces, zoals adapter voor stopcontact) Oplossing: 1) klassenadapter: gebruik van overerving (erft over van de om te vormen klasse en implementeert de interface -> methodes delegeren) 2) objectadapter: gebruik van compositie (implementeert interface en bezit het om te vormen object -> methodes delegeren) Gevolgen: Klassenadapter: - geen adaptie van subklassen - geen extra objecten - overerving (past gedraag aan) Objectadapter: - automatische adaptie aan de subklassen - gemakkelijk functionaliteit toevoegen aan complete hiërarchie van geadapteerde klassen - geen overerving (voordelen polymorfisme, maar dubbel zo veel objecten aangemaakt) - geadapteerde klasse kan at run-time vervangen worden
2.2 Decorator Probleemstelling: Features toevoegen aan objecten (eerder dan aan klassen, dus overerving niet mogelijk). Overerving zou hier zorgen voor een enorm aantal klassen (alle mogelijk combinaties). Features toevoegen en wegnemen at run-time. Toevoeging van een nieuw object met mogelijke features (die er al waren) -> opnieuw tijdrovend! Oplossing: - features zijn subtypes van een Decorator klasse - decorator (net zoals een concrete component) is een Component (overerving) en bevat een component -> concateneren van features mogelijk
-> niet zichtbaar dat het een wrapperobject is voor de cliënt (zelfde methodes beschikbaar) -> functies voorzien door delegeren naar component object Toepasbaarheid: - dynamisch toevoegen en verwijderen van features - features bij object in plaats van bij klasse - transparant toevoegen van features - features toevoegen zonder subclassing Gevolgen: - flexibiliteit t.o.v. statische subclassing (intrekken features, 2x toevoegen..) - geen klassen overladen met features - gedecoreerd object niet indentiek aan origineel object - vele kleine objecten bv. IO-package/scrollbars
2.3 Proxy Probleemstelling: Toegang tot een object omleiden (shielden) -> een surrogaat voorzien om: - creatie uit te stellen (placeholders die snel laden) - references te tellen (c-implementatie garbage collector) Wanneer toegang tot een object problematischer is dan een methode of een constructor oproepen om het object te creëren. Oplossing: Voor elke te shielden klasse, een dummyklasse (de proxy) voorzien als placeholder, deze aggregeert de te shielden klasse en delegeert de methodes. Klasse en proxy voldoen aan eenzelfde interface die gebruikt wordt door de cliënt. Toepasbaarheid: - Gecontroleerde toegang o Remote proxy: lokale vertegenwoordiger voor object op afstand o Virtual proxy: op vraag aanmaken van zwaar object o Protection proxy: toegangscontrole vanuit verschillende objecten o Smart References: (smart pointer) § Tellen van references -> bij 0 vernietigen -> garbage coll. § Persistentie management (bewaren en ophalen van objecten naar/uit een permanente staat wanneer nodig) § Lock checking (dirty read/wirte vermijden bij multithreading)
2.4 Bridge Probleemstelling: subtyping om 2 redenen: - abstracties verfijnen/uitbreiden - concrete implementaties voor abstracties - Voor elke verfijning moeten alle implementaties opnieuw gebeuren => groot aantal klassen. - Iedere keer als de abstractie veranderd -> hercoderen relatie met implementatie. (weinig hergebruik mogelijk) - Niet flexibel: binding aan implementatie is permanent
Oplossing: - ontkoppelen van de hiërarchie, in twee afzonderlijke hiërarchieën -> koppeling door bridge: zorgt voor concrete implementatie bij specifiek object. - Bovenste object in abstractie-hiërarchie houdt een referentie bij naar een implementatiegerelateerd object => dynamisch switchen mogelijk. - Enkel top-level koppeling! Methodes delegeren naar implementatiespecifieke methodes. - Eventueel een implementatiefactory (singleton) die het implementatieobject bijhoudt en veranderen mogelijk maakt. Toepasbaarheid: - vermijden van statische, permanente verbinding tussen abstractie en implementatie. - Abstractie verfijnen en implementatie hiërarchieën splitsen - Cliënt afgeschermd van de implementatie, ziet enkel abstractieverfijning -> onafhankelijkheid van implementatie - Kleiner aantal klassen
2.5 Composite Probleemstelling: situaties met containerstructuren binnen een hiërarchie, twee delen: objecten van containerklassen die andere objecten kunnen bevatten en objecten van atomaire componentklassen die geen objecten kunnen bevatten. Containerklassen gespecialiseerd in containers en containerklassen gespecialiseerd in atomair => containerklassen kunnen geen containers bevatten, tenzij gespecialiseerde klassen worden gemaakt. Verkieslijk is een recursief systeem, waarbij containers elke mix van objecten kunnen bijhouden. Oplossing: - Recursief te werk gaan - Paraplutype maken (Component-interface met gemeenschappelijke methodes: toevoegen, verwijderen, kinderen ophalen – bij container toepassen (delegeren) op elk van zijn bevatte elementen) -> geïmplementeerd door zowel containertype als atomair type. - Containertype bevat elementen van het type Component (atomair of niet). Toepasbaarheid: - relaties waarbij containers een rol spelen en bij boomstructuren - natuur van een object is verborgen voor de gebruiker (zelfde operaties) Gevolgen: - Gemakkelijk toevoegen van nieuwe componenten (enkel component uitbreiden) - Moeilijk restricties op bevatte objecttypes op te leggen -> RTTI nodig Implementatie: - Container moet verantwoordelijk zijn voor vernietiging kinderen (in de remove-methode) - Verwijzing naar parent bijhouden om problemen bij verwijderen te voorkomen (anders kunnen problemen voorkomen)
2.6 Flyweight Probleemstelling: Veel logische kopieën van bijna identieke objecten in een systeem (bv. tekstverwerker waar letters objecten zijn) => oplossing als dit gelimiteerd zou moeten worden. Oplossing: bij niet wijzigbare objecten, meerdere referenties naar zelfde object toelaten, zodat er geen identieke objecten meer voorkomen. Anders, overeenkomende delen als referentie nemen, andere delen voor elk object afzonderlijk bijhouden (vooral van toepassing wanneer er een grote overeenkomst is)
2.7 Façade Probleemstelling: hoe een unieke interface voorzien voor een subsysteem (communicatieklasse) -> dit is ideaal één enkele klasse, hoe doen we dit? Oplossing: Façadeklasse voorziet de interface en delegeert de methodeoproepen naar andere klassen in het systeem. Façade is de enige publieke klasse in het subsysteem.
3. Behavioral (=interacHe tussen objecten) Beschrijven van de verdeling van verantwoordelijkheden en typische communicatie tussen klassen en objecten om een probleem op te lossen. Veel gebruik maken van compositie voor de verdeling. Klasse patronen: gebruiken overerving! (template + interpreter) Object patronen: - losse koppeling (minder afhankelijk) tussen communicerende objecten: mediator/Observer/Chain of Responsibility/Command - gedrag verpakken in een object om een OO probleem op te lossen: strategy -> inplugbare algoritmische componenten gemakkelijk collecties overlopen -> iterator finite state machines -> state command/visitor - Foto nemen van huidige status: Memento
3.1 Mediator Probleemstelling: interactie tussen een groot aantal objecten - minder herbruikbare software (klassen kunnen niet in isolatie werken, want ontworpen voor communicatie met een groot aantal andere klassen) - verspreiding van interactielogica over het hele systeem Oplossing: alle interactie bundelen in één object (de mediator) dat dient als centrale coördinator, deze kent alle interagerende objecten. Andere interagerende klassen heten colleagues en refereren enkel naar de mediator (alle communicatie moet daar passeren). Alle klassen die interactie willen moeten de klasse Colleague uitbreiden (deze bevat een changed() methode om aan te geven dat er iets veranderd is en een setMediator() methode om de mediator in te stellen) De mediator zelf bevat een methode changed(c: Colleague) om aan te geven dat
c een verandering heeft doorgevoerd, eventueel een concrete mediator (als met uitbreiding gewerkt wordt) bevat dan alle methodes voor de interactie. Bevat ook een collectie van alle interagerende objecten. Gevolgen: - limiteert subclassing: enkel mediator moet uitgebreid worden om gedrag te veranderen in plaats van alle colleagues. - Onafhankelijkheid tussen colleagues, ook abstracte mediatorklassen kunnen herbruikt worden. - Eenvoudigere logica: veel -> veel interactie wordt nu één -> veel - Interactie tussen colleagues in één klasse => splitsing eigen gedrag en gedrag i.v.m. samenwerking => gemakkelijke identificatie en verandering van interactiepatronen. - Mediator kan zeer complex worden Implementatie: - abstracte mediator zorgt ervoor dat deze hergebruikt kan worden, laat gebruik van verschillende concrete mediators toe, elk met een eigen interactiepatroon tussen colleagues. - Communicatie gebeurt meestal door observer (subject = colleague, observer = mediator) en soms door specifiek notificatiesysteem (spoor van veranderingen volgen)
3.2 Observer (= Publish – subscribe) Probleemstelling: onafhankelijkheid tussen communicerende componenten. Op een efficiënte manier één -> veel relaties organiseren. Oplossing: een algemene interface, in plaats van een specifieke voor elk paar communicerende klassen. Luisteraars dynamisch bijhouden en verwittigen bij veranderingen. Veel gebruikt in MVC, event-gebaseerde systemen en mediator. Observer-interface moet geïmplementeerd worden door de klassen die willen luisteren (update() methode overschrijven -> wat moet er gebeuren bij verwittiging?). Subject bevat de methodes om een observer toe te voegen, te verwijderen en op de hoogte te brengen (met als parameter het subject -> callback om te vragen welke verandering zich heeft voorgedaan, of een Event object met informatie, veelal ook een referentie naar subject), wordt uitgebreid door een specifiek te volgen object. Gevolgen: - Hergebruik van Subject/observer, want abstracte koppeling. (ook binnen eenzelfde systeem, hergebruik mogelijk) - Maakt verwittigingsmechanisme gemakkelijker, bestemming kan bepaald worden at run-time. - Onverwachte updates, subject kent het effect niet van de updatemethode die hij oproept -> opletten dat notify niet te vaak gebeurt bij complexe methodes. Implementatie:
-
-
Bij een klein aantal observers tegenover een groot aantal subjecten -> HashMap voor bijhouden verbindingen Bij observeren meer dan 1 subject -> identificeren door referentie subject Verantwoordelijke voor oproepen van notify? o Subject -> consistentie <-> groot aantal updates o Cliënt -> klein aantal updates <-> gevaar voor consistentieverlies Subject moet observers verwittigen voor destructie, anders dangling pointers EERST waarde veranderen DAN notify. Push vs pull : o info meegezonden in notify bij push -> verminderd hergebruik/ ongewilde afhankelijkheid. o Info opgevraagd bij subject na notify -> overhead Probleem met te veel aan updates oplossen: o Registreren voor specifieke gebeurtenissen o Zeer specifieke updates ChangeManager voor updatebeheer: kan specifieke problemen (zoals oneindige lus van updates) detecteren en opvangen.
3.3 Chain of responsibility Probleemstelling: - Ontkoppelen verzender en afhandeling van vraag - (gelinkte) set van handlers kunnen de vraag afhandelen: een willekeurig eerste object krijgt de vraag en geeft hem eventueel door aan een volgende handler als hij hem zelf niet afwerkt. - Laat gemakkelijk hergebruik toe, handlers kunnen opnieuw gebruikt worden (in andere kettingen) - Handler houdt zijn opvolger bij, uitgebreid door concrete handlers. NoHandlerFoundException als er geen opvolger meer is en de vraag niet afgehandeld werd. Gebruik: - als handler vooraf niet gekend is - als de cliëntcode onafhankelijk moet zijn van de handler - als de ketting dynamisch moet veranderd kunnen worden Gevolgen: - Verminderde koppeling: cliënt kent de uiteindelijke handler niet, ook in handlers geen koppelingen (gemakkelijk onderhoud) - Flexibele verdeling van verantwoordelijkheden (ketting kan at-runtime veranderen) - Geen garantie dat de vraag afgehandeld wordt Implementatie: - Ketting kan bestaan uit nieuwe links (in vorm van linked list meestal) en uit oude links (in een bestaande structuur: bv. containerhiërarchie) - Eventueel meegeven van een parameter aan de handler om een specifieke wens uit te drukken (type vraag)
3.4 Command
Probleemstelling: Een actie als object vermommen. Elk commando stelt een specifieke vraag voor van een cliënt aan de server (worden zo ontkoppeld, ook in tijd -> server kiest wanneer het commando af te handelen) Laat toe om een wachtrij te gebruiken, te loggen en undo/redo te implementeren. Vb. Interface toolkit -> specifieke inhoud/betekenis van commando niet gekend bij ontwikkelen van de toolkit. Oplossing: - interface Command met een methode execute() die het commando uitvoert -> resultaten bewaard in concrete commands - een receiver object die de logica implementeert -> specifieke commando’s delegeren hun methodes naar dit object (houden dus referentie bij) -> wordt meegegeven aan commando door cliënt - een invoker waaraan de commando’s worden gegeven ter uitvoering
Gevolgen: - Ontkoppeling van invoker en logische implementatie (zowel functioneel, als in tijd) - Commando’s als objecten - Composite gebruikt voor samengestelde commando’s - Gemakkelijk toevoegen van nieuwe commando’s Implementatie: - intelligentie van commando’s kan variëren: o enkel delegatie naar receiver o doet alles zelf o dynamisch zoeken naar gepaste receiver -> helpt ontkoppeling in tijd (bv. op web zoeken naar receiver die op dat moment het snelst reageert) - Undo/Redo implementeren -> opletten! Bij verkeerde undo krijg je een opstapeling van fouten. Undo/Redo: - voorzien in de command interface - technieken: o inverse operatie voorzien (niet altijd perfect mogelijk) o snapshot nemen (met memento) van alle gerelateerde objecten en hiernaar terugkeren bij oproepen undo -> beperken van aantal gerelateerde objecten! - Bij ondersteuning voor meerdere undo’s, bijhouden van een lijst met diepe kopieën van commando’s (met bv. prototype) -> anders kan undo fouten veroorzaken
3.5 Strategy Probleemstelling: selectie van alternatieve algoritmes die behoren tot eenzelfde familie at run-time. Gebruik: - klassen/objecten configureren met gedrag: veel alternatieve gedragingen, elk alternatief = strategy - verschillende smaken van algoritmes : gespecialiseerd in situaties - verberg algoritme specifieke data voor cliënt - loskoppeling algoritme en data -> concentratie op één aspect tegelijk Oplossing: - abstracte klasse die eventueel context bevat (data) en eventueel een default algoritme - concrete klassen die een algoritme implementeren Gevolgen: - Gemakkelijk schikken in hiërarchie van algoritmes (vermijden codeduplicatie) - Niet onmiddellijk context uitbreiden, want: o Niet dynamisch configureerbaar -> algoritme beslist bij aanmaak context. o Veel klassen -> weinig verschil o Algoritme moeilijk te hergebruiken.
-
Geen gebruik van if-else om te bepalen welk algoritme te gebruiken, want minder flexibel, minder hergebruik en moeilijk begrijpbaar - Run-time optimalisatie: beste algoritme selecteren op basis van runtime condities. - Cliënt moet wel de verschillende strategieën kennen -> gebruik van defaultstrategie aangeraden. - Overhead door communicatie tussen strategie en context - Groter aantal (kleine) objecten. Implementatie: - Twee mogelijkheden: o Alle data meegeven in methodeoproepen -> kan voor overhead zorgen, zeker bij groot aantal oproepen. o Referentie naar context meegeven aan abstracte strategy : enkel data opvragen die nodig is, maar meer methodeoproepen tussen context en strategy (impliceert vertraging) , grotere koppeling tussen context en strategy (minder hergebruik) en context moet een call-back interface voorzien. - Optioneel meegeven van strategy, anders default gebruiken (net als bij java collections raamwerk)
3.6 Iterator Probleemstelling: algemene methode voor het overlopen van objecten in een geaggregeerd object, verbergen van de onderliggende implementatie. Gebruik: - Gemakkelijke overlopen van objecten zonder implementatie te tonen - Meerdere manieren om objecten te overlopen - Polymorfisme bij overlopen Oplossing: - Iterator-interface voor overlopen, geïmplementeerd door een concrete implementatie als binnenklasse (of friend) van een te overlopen klasse. Gevolgen: - Te overlopen klasse kan op verschillende manieren overlopen worden (afhankelijk van de gebruikte implementatie van de concrete iterator) - Iteratie ligt buiten de te overlopen klasse => eenvoudigere interface - Verschillende simultaan actieve iterators mogelijk => rekening mee houden, dat ze geen ongewenste effecten op elkaar hebben. Implementatie: - Controle over de iteratie: o Extern: cliënt past vraagt de iterator een stap vooruit te gaan, achteruit te gaan.. enz -> complexer, maar algemener o Intern: operatie aan de iterator geven -> iterator past deze operatie toe op elk element -> eenvoudiger, maar niet algemeen - Soms bevat het te itereren object ook het algoritme om te itereren -> iterator enkel dienst als cursor - Roobuustheid: gevaarlijk om object waarover geïtereerd wordt te veranderen tijdens itereren, op te lossen door te itereren over kopie of elke lopende iterator op de hoogte te brengen van de verandering (waarop deze zich aanpast)
-
Bijkomende methodes: skipTo, previous... Standaard: first(), next(), hasNext(), currentItem() in klasse om over te itereren: createIterator() Bij complexe structuren: o Externe iterator: pad bijhouden doorheen de recursieve structuur o Interne iterator: gemakkelijk door recursieve oproep Als de te itereren structuren zelf in een te itereren structuur zijn opgenomen wordt een nulliterator teruggegeven wanneer het einde bereikt is.
3.7 State Probleemstelling: objectgedrag veranderen op basis van interne structuur, terwijl vermeden wordt telkens de statusvariabelen te moeten controleren in iedere methode. Gebruik: - modelleren van een deel van het gedrag van een applicatie -> Finite State Machines voorstellen - wanneer objectgedrag sterk afhangt van run-time status -> veel statustests nodig! (moeilijk vatbaar/moeilijk onderhoud) Oplossing: - context bevat de huidige status (d.m.v. setState()) in de vorm van een State-interface (geïmplementeerd door concrete statussen). - Request() methode in context wordt gedelegeerd naar huidige status Gevolgen: - Lokaliseert status-specifiek gedrag: meer klassen, maar veel overzichtelijkere code (grote overeenstemming met design van de FSM) - Maakt overgangen specifiek: beveiliging tegen inconsistente statusinformatie, meer expliciet dan variabelen veranderen - Delen van statusobjecten om overhead te vermijden (zeker bij groot aantal en als ze zelf geen variabelen bevatten) Implementatie: - Statusovergangen maken: gemakkelijk bij vaste sequentie o Vaak gezet op einde van handle() methode in status zelf, maar.. -> afhankelijkheden o Oplossing: zetten in context, gemakkelijk als nieuwe status enkel afhangt van oude status, anders: mogelijk, maar verminderde koppeling met desing. - Eventueel opzoeken van volgende status in tabel: flexibel (FSM kan veranderd worden at run-time), maar overhead door opzoeking bij elke overgang. - Aanmaken statusobjecten op voorhand voor hogere snelheid wanneer statussen hergebruikt kunnen worden.
3.8 Memento Probleemstelling: status van een object vastleggen zonder de inkapseling te schaden, gemakkelijker terugkeren naar eerdere statussen. Gebruik: - publieke interface onvoldoende om status vast te leggen
-
checkpointing bij grote berekeningen (deelberekeningen bewaren op veilige locatie, zodat berekening niet volledig opnieuw moet gebeuren bij crash) - undo In java (en c#) kan hiervoor serialisatie gebruikt worden (dit patroon niet nodig!) Oplossing: - Memento-klasse bewaart de interne status van een object (originator: createMemento() en setMemento() -> instellen status), moet daarvoor toegang hebben tot private attributen (dus bevat setState(), op te roepen door originator) - Caretaker staat in voor het bijhouden en beheren van de memento’s (maar inspecteert of manipuleert de inhoud niet) -> ook de bevoegdheid om een object naar vroegere status te herstellen. (roept hiervoor setMemento() van originator op, die op zijn beurt getState van memento gebruikt) Gevolgen: - inkapseling blijft behouden -> status van originator niet voor iedereen zichtbaar - beperktere interface voor originator, version management wordt geregeld door de caretaker -> onafhankelijkheid en hergebruik - potentieel duur door diepe kopie, gedeeltelijk op te lossen door incrementele kopieën. - Onderscheid brede en smalle interface nodig in implementatietaal (private/public) - Verborgen kosten: caretaker heeft geen besef van de impact van het bewaren van de memento (want inkapseling blijft behouden) Implementatie: - onderscheid tussen brede en smalle interface in elke moderne OO-taal - memento’s relatief aan elkaar om kost te beperken
3.9 Template Probleemstelling: Hoe sequentie van stappen in een algoritme coderen zonder de stappen zelf te specifiëren? (gebruikt in raamwerken zoals Java Applet) Oplossing: Abstracte klasse die het algoritme voorziet en de methodes van de stappen, maar dan abstract gemaakt. Concrete implementatie van die stappen in een subklasse.