1 Inleiding tot Design Patterns
h
Welkom bij de g Design Patterns
g
Nu we in Objectville wonen, moeten we ook met de Design Patterns kennismaken ... dat doet hier iedereen. We zullen gauw de ster zijn van de woensdagavond patternsgroep van Jim en Betty.
Iemand heeft je problemen al eens opgelost. In dit hoofdstuk leer je waarom (en hoe) je de kennis en ervaring kunt inzetten van andere ontwikkelaars die dezelfde weg bij het ontwerpprobleem hebben afgelegd en deze trip hebben overleefd. Eerst bekijken we het gebruik en de voordelen van Design Patterns, bespreken we enkele belangrijke OO-ontwerpprincipes, en wandelen we door een voorbeeld om te zien hoe een pattern werkt. De beste manier om patterns te gebruiken is ze uit het hoofd te leren, om vervolgens de plaatsen waar je ze kunt toepassen te herkennen in jouw ontwerpen en in bestaande applicaties. In plaats van hergebruik van code krijg je hergebruik van ervaring.
Dit is een nieuw hoofdstuk
421_01_nl.indd 1
1
30-04-2007 22:58:11
SimUDuck
Het begon met een eenvoudige SimUDuck-toepassing Joe werkt voor een bedrijf dat veel succes heeft met met een simulatiespel van een eendenvijver, SimUDuck. Het spel kan een groot aantal verschillende eendensoorten tonen die rondzwemmen en kwaken. De oorspronkelijke ontwerpers van het systeem gebruikten standaardOO-technieken en creëerden een superklasse Duck waar alle andere eendentypen van afgeleid zijn.
Duck
Alle eenden kwaken en zwemmen, de superklasse zorgt voor de implementatiecode.
quack() swim() display() // ANDERE eend-achtige methoden ...
k n duc or a v e o p subty elijk v Ieder antwoord tie van is ver plementa n zijn de im edrag va om te het g display() deze op eigen zien hoe rschijnt. laten cherm ve het s
MallardDuck
RedheadDuck
display() {
display() {
// lijkt op een wilde
// lijkt op een
// eend }
// roodkuifeend }
is De methode display() e all ien ez abstract, aang subtypen van eend er verschillend uitzien.
ven
n er orten eende so e r e d n a l Vee e Duck. van de klass
Het afgelopen jaar heeft het bedrijf steeds meer last van de concurrentie. Na een week lang brainstormen tijdens een heisessie, denkt de bedrijfsleiding dat het tijd wordt voor een grote innovatie. Ze moeten echt iets geweldigs laten zien op de komende aandeelhoudersvergadering volgende week in Maui.
2
421_01_nl.indd 2
Hoofdstuk 1
30-04-2007 22:58:22
Inleiding tot Design Patterns
Nu moeten we de eenden leren vliegen De bedrijfsleiding besloot dat vliegende eenden in de simulator precies datgene was dat concurrerende eendensimulators zou wegvagen. En natuurlijk vertelde de manager van Joe dat het geen probleem zou zijn om dit in een weekje voor elkaar te krijgen.‘Tenslotte’, zei de baas van Joe, ‘is hij een OO-programmeur ... hoe moeilijk kan dat nu zijn.’ Ik hoef alleen een methode fly() aan de klasse Duck toe te voegen en alle eenden zullen haar erven. Nu is het de tijd om te laten zien hoe goed ik in OO ben.
Joe
Dit willen we. Duck quack() swim()
n klasse b u s Alle n fly(). erve
MallardDuck
display() fly() // ANDERE eend-achtige methoden ...
RedheadDuck
display() {
display() {
// lijkt op een
// lijkt op een
// wilde eend }
// roodkuifeend }
oe Dit heeft J . d g e toegevo
densoorten
Andere een
…
Je bent hier 4
421_01_nl.indd 3
3
30-04-2007 22:58:23
Er ging iets mis
Maar er liep iets helemaal mis Joe, ik ben hier op de aandeelhoudersvergadering. Ze hebben net een demo getoond en er vlogen badeenden over het scherm. Is dat een grap of zo? Misschien moet je eens op monster.com gaan rondkijken!
Wat is er gebeurd? Joe had er niet aan gedacht dat niet alle subklassen van Duck mogen vliegen. Toen Joe nieuw gedrag aan de superklasse Duck toevoegde, voegde hij tevens gedrag toe aan een aantal subklassen van Duck waarvoor dat niet van toepassing was. Het gevolg was dat er nu niet-vliegende objecten in het programma SimUDuck gingen vliegen.
OK, er zit een klein foutje in mijn ontwerp. Ik begrijp overigens niet waarom ze dat niet gewoon een ‘feature’ noemen. Op zich is dat toch heel leuk ...
Hij had in verband met hergebruik aan overerving gedacht, hetgeen met het oog op onderhoud niet zo goed uitpakte.
Een lokale update zorgde voor niet-lokale neveneffecten (vliegende badeenden)!
Duck
sse erkla p u s de () in orgde hij den y l f Door laatsen, z LLE een f te p or dat A n, inclusie ervo en vliege dat niet kond nen die diege n. moge MallardDuck
quack() swim() display() fly() // ANDERE eend-achtige methoden ...
ReadheadDuck
display() {
display() {
// lijkt op een
// lijkt op een
// wilde eend }
// roodkuifeend }
RubberDuck quack() { // overridden door Squeak } display() {
aken Badeenden kwdt niet, dus wor idden quack() overr’. door ‘Squeak
// lijkt op een badeend }
4
421_01_nl.indd 4
Hoofdstuk 1
30-04-2007 22:58:25
Inleiding tot Design Patterns
Joe denkt over overerving na .... Via override zou ik natuurlijk de methode fly() voor de badeend kunnen vervangen, op dezelfde manier als bij de methode quack() ...
Maar wat gebeurt er dan wanneer we houten lokeenden aan het programma toevoegen? Die vliegen en kwaken ook niet ...
RubberDuck quack() { // squeak} display() { // badeend} fly() { // override, zodat ze niets // doen }
DecoyDuck quack() { // override, zodat ze niets doen } display() { // lokeend}
in de ere klasse ze net d n a n e e Dit is ; merk op dat hiërarchie eend niet vliegt en als de bad kwaakt ze niet. bovendien
fly() { // override, zodat ze niets doen }
Slijp je potlood Welke van de volgende alternatieven zijn nadelig voor het gebruik van overerving om voor het gedrag van een eend te zorgen? (Meerdere alternatieven zijn mogelijk.)
❏ A. Code wordt in de subklassen gedupliceerd. ❏ D. Het is moeilijk om het gedrag van alle eenden te kennen. ❏ B. Runtime gedragsveranderingen zijn zéér ❏ E. Eenden kunnen niet tegelijk vliegen en kwaken. moeilijk/onmogelijk. ❏ F. Veranderingen kunnen onbedoeld andere eenden ❏ C. We kunnen eenden niet laten dansen. beïnvloeden. Je bent hier 4
421_01_nl.indd 5
5
30-04-2007 22:58:27
Overerving is niet het antwoord
Hoe zit dat met een interface? Joe realiseert zich dat overerving vermoedelijk niet het antwoord is, omdat hij zojuist een memo heeft ontvangen waarin staat dat de leiding nu wil dat het product voortaan ieder zes maanden wordt geüpdatet (op welke manier is nog niet besloten). Joe weet dat de specificaties blijven veranderen en dat hij gedwongen zal zijn naar de methoden fly() en quack() te kijken en deze via een override zal moeten vervangen, iedere keer dat er een nieuwe subklasse aan Duck wordt gehangen ... en wel voor eeuwig.
Ik zou fly() uit de superklasse Duck kunnen halen en een interface Flyable kunnen maken met een fly()-methode. Op die manier zullen alleen eenden die kunnen vliegen deze interface implementeren en over een methode fly() beschikken ... en ik zou ook een interface Quackable kunnen maken omdat niet alle eenden kunnen kwaken.
Hij heeft dus een betere manier nodig waarbij slechts een aantal (maar niet alle) eendensoorten vliegen en kwaken.
Duck swim()
Quackable
Flyable
display()
quack()
fly()
MallardDuck
// ANDERE eend-achtige methoden ...
ReadheadDuck
RubberDuck
display()
display()
display()
fly()
fly()
quack()
quack()
quack()
DecoyDuck display()
Wat denk jij van dit ontwerp?
6
421_01_nl.indd 6
Hoofdstuk 1
30-04-2007 22:58:30
Inleiding tot Design Patterns Dat is wel ongeveer het domste dat je kunt doen. Heb je al eens van ‘codeduplicatie’ gehoord? Als je al dacht dat het vervangen van een paar methoden via override een slecht idee was, hoe denk je dan over het aanbrengen van een paar kleine veranderingen in het vlieggedrag ... in alle 48 vliegende subklassen van Duck?!
Wat zou jij doen als je Joe was? We weten dat niet alle subklassen vlieg- of kwaakgedrag moeten vertonen, dus is overerving niet het juiste antwoord. Weliswaar wordt via de implementatie van Flyable en/of Quackable in de subklassen een deel van het probleem opgelost (zodanig dat badeenden niet meer kunnen vliegen), maar het concept van hergebruik van code voor een dergelijk gedrag wordt onmogelijk en zo ontstaat een andere nachtmerrie voor het onderhoud. Bovendien kunnen er meerdere soorten vlieggedrag voorkomen bij eenden die wel kunnen vliegen ... Op dit punt gestrand, zou je kunnen wachten tot er een Design Pattern op een wit paard aan komt galopperen en je dag weer goed maakt. Maar wat is daar voor lol aan? Nee, we gaan maar eens op de ouderwetse manier een oplossing verzinnen ... door de juiste softwareontwerpprincipes volgens OO toe te passen.
Zou het niet mooi zijn als er een manier bestond om software zo te bouwen dat wanneer we deze moeten veranderen, we dit met zo weinig mogelijk gevolgen voor de bestaande code kunnen doen? We zouden dan minder tijd besteden aan het opnieuw bewerken van de code en meer tijd hebben om het programma nog cooler te maken ...
Je bent hier 4
421_01_nl.indd 7
7
30-04-2007 22:58:31
Veranderen is de constante
De enige constante bij soft wareont wikkeling Oké, wat is het enige ding waar je bij softwareontwikkeling altijd op kunt rekenen? Los van wat je maakt of in welke taal je programmeert, wat is de enige constante die je altijd tegenkomt?
GNIREDNAREV (Gebruik een spiegel om het antwoord te lezen). Het maakt niet uit hoe goed je een applicatie ontwerpt, na verloop van tijd moet zij groeien of veranderen, want anders zal de applicatie afsterven.
Slijp je potlood
Er zijn een hoop oorzaken die tot veranderingen kunnen leiden. Maak een lijst met redenen waarom je de code van applicaties moest veranderen (we hebben er maar vast een paar genoemd om je op weg te helpen).
Mijn klanten of gebruikers vonden dat ze iets anders wilden, of ze wilden nieuwe functionaliteit. Mijn bedrijf besloot op een andere databaseleverancier over te stappen en koopt ook zijn gegevens van een andere leverancier die een verschillend formaat gebruikt. Oei!
8
421_01_nl.indd 8
Hoofdstuk 1
30-04-2007 22:58:32
Inleiding tot Design Patterns
Terug naar het probleem We weten nu dat overerving niet goed heeft uitgepakt, aangezien het gedrag van de eenden in de diverse subklassen verandert en we het gedrag niet in alle subklassen kunnen toepassen. Het gebruik van een Flyable- en Quackable-interface klonk in eerste instantie veelbelovend – alleen eenden die echt kunnen vliegen kunnen Flyable zijn, enzovoort – maar Java-interfaces kennen geen implementatiecode en is er zo geen hergebruik van code. En dat betekent weer dat wanneer je een bepaald gedrag moet aanpassen, je gedwongen bent om dit op te sporen en en te veranderen in alle subklassen waarin dit gedrag gedefinieerd wordt; op deze manier worden waarschijnlijk nieuwe fouten geïntroduceerd! Gelukkig bestaat er ook voor deze situatie een ontwerpprincipe. Ontwerpprincipe Bepaal de aspecten van je applicatie die variëren en scheid deze van de aspecten die hetzelfde blijven.
Dit is de eerste van vele ontwerpprincipes. We besteden daar het hele boek door meer tijd aan.
Neem datgene dat varieert en isoleer dit, zodat de rest van je code niet wordt beïnvloed . Het gevolg? Minder onbedoelde effecten door veranderingen in de code en een grotere flexibiliteit in jouw systemen!
Anders gezegd, bevat je code een aspect dat verandert, bijvoorbeeld met iedere nieuwe systeemeis, dan weet je dat je een gedrag hebt dat eruit gelicht moet worden en moet worden afgezonderd van alle code die niet verandert. We kunnen dit principe ook op een andere manier benaderen: verzamel de delen die variëren en isoleer deze. Op deze wijze kun je de delen die variëren, veranderen of uitbreiden zonder dat de delen die niet veranderen worden beïnvloed. Hoe eenvoudig dit concept ook is, het vormt de basis voor bijna ieder design pattern. Alle patterns voorzien in een manier om bepaalde systeemdelen onafhankelijk van de andere delen te veranderen. Mooi, het wordt tijd om het eendengedrag uit de subklassen van Duck te lichten!
Je bent hier 4
421_01_nl.indd 9
9
30-04-2007 22:58:32
Het eruit lichten van veranderlijke delen
Scheid wat verandert van wat hetzelfde blijft Waar zullen we beginnen? Zover we weten werkt de klasse Duck goed, afgezien van de problemen met fly() en quack(), en zijn er geen andere delen die zouden moeten variëren of regelmatig veranderen. Dus afgezien van enkele kleine veranderingen laten we het grootste deel van de klasse Duck met rust. Nu gaan we dus de delen die veranderen scheiden van de delen die hetzelfde blijven. We maken nu, volledig gescheiden van de klasse Duck, twee sets met klassen, een voor fly en een voor quack. Iedere set klassen gaat de volledige implementatie van hun respectievelijke gedrag bevatten. Zo kunnen we een klasse hebben voor de implementatie van quacking, een andere klasse voor de implementatie van squeaking en weer een andere klasse voor de implementatie van silence.
We weten dat fly() en quack() de delen van de klasse Duck zijn die voor eenden verschillen. Om dit gedrag van de klasse Duck te scheiden, halen we beide methoden uit de klasse Duck en maken we een nieuwe set klassen die het betreffende gedrag vertegenwoordigen.
steeds de De klasse Duck is nog nden, maar we superklasse van alle ee en het gedrag lichten het gedrag fly deze in een quack eruit en plaatsenr. andere klassestructuu
het Nu krijgen het vliegen enset klassen. en eig n ee er kwaken ied
Hier staat de implementatie van de diverse soorten gedrag.
Eruit lichten wat kan veranderen.
k Klas se Duc
Vlie
ggedrag
Kw
aakge ag dr
Eendengedrag
10
421_01_nl.indd 10
Hoofdstuk 1
30-04-2007 22:58:32
Inleiding tot Design Patterns
Het eendengedrag ont werpen Hoe moeten we nu de set klassen voor de implementatie van het gedrag fly en quack ontwerpen? We willen flexibel blijven; het was uiteindelijk juist de inflexibiliteit van het eendengedrag die ons in moeilijkheden bracht. En we weten dat we gedrag aan de instanties van Duck willen toekennen. We willen wellicht een nieuwe instantie van MallardDuck concretiseren en deze initialiseren met een bepaald soort vlieggedrag. En als we dan toch bezig zijn, waarom zorgen we er dan niet voor dat we het gedrag van een eend dynamisch kunnen veranderen? Anders gezegd, we moeten methoden voor het instellen van het gedrag in de klassen van Duck opnemen, zodat we bijvoorbeeld het vlieggedrag van de klasse MallardDuck at runtime kunnen veranderen. Met dit doel voor ogen bekijken we het tweede ontwerpprincipe:
Van nu af aan staat het gedrag van Duck in een afzonderlijke klasse – een klasse die een bepaalde gedragsinterface implementeert.
Ontwerpprincipe Programmeer naar een interface, niet naar een implementatie. We gaan voor ieder gedrag een interface gebruiken – bijvoorbeeld FlyBehavior en QuackBehavior – en iedere implementatie van een gedrag implementeert een van deze interfaces. Dus deze keer zijn het niet de klassen van Duck die de interfaces voor het vliegen en kwaken implementeren. In plaats daarvan maken we een set klassen waarvan de enige bestaansreden is dat ze een gedrag voorstellen (bijvoorbeeld ‘squeaking’). De klasse met het gedrag en niet de klasse Duck implementeert de gedragsinterface. Dit staat in contrast tot de manier waarop we de zaken tot dusver regelden, waarbij we het gedrag realiseerden of via een concrete implementatie in de superklasse Duck, of voor een specifieke implementatie in de subklassen zelf. In beide gevallen vertrouwden we op een implementatie. We maakten ons afhankelijk van een specifieke implementatie en er was geen ruimte voor het veranderen van het gedrag (anders dan door meer code te schrijven). In ons nieuwe ontwerp gebruiken we voor de subklassen van Duck het gedrag dat door een interface wordt voorgesteld (FlyBehavior en QuackBehavior); de actuele implementatie van het gedrag (dus de specifieke code voor het gedrag in de klasse die FlyBehavior of QuackBehavior implementeert) zit niet langer opgesloten in de subklassen van Duck.
Op deze manier hoeven de klassen van Duck niets te weten van de implementatiedetails van hun eigen gedrag. <
>
Flybehavior
fly()
FlyWithWings
FlyNoWay
fly() {
fly() {
// implementeert het vliegen
// doe niets - kan niet
// van eenden
// vliegen // }
Je bent hier 4
421_01_nl.indd 11
11
30-04-2007 22:58:33
Naar een interface programmeren Ik begrijp niet waarom je een interface voor FlyBehavior moet gebruiken. Je kunt hetzelfde bereiken via een abstracte superklasse. Draait het daar bij polymorfisme niet om?
‘Programmeren naar een interface’ betekent feitelijk ‘Programmeren naar een supertype’. Het woordje interface heeft een dubbele betekenis. Maar dit is geen toeval. We kennen het concept interface, maar er bestaat ook een constructie met de naam interface in Java. Je kunt naar een interface programmeren zonder de interface van Java te gebruiken. Het gaat er om zodanig van polymorfisme gebruik te maken door naar een supertype te programmeren zodat het actuele runtimeobject niet is opgesloten in de code. We kunnen de uitdrukking ‘programmeren naar een supertype’ vervangen door ‘het gedeclareerde type van de variabele dient een supertype te zijn, doorgaans een abstracte klasse of een interface, zodat de objecten die aan dergelijke variabelen worden toegekend, iedere concrete implementatie van het supertype kunnen zijn; dat betekent dat de klasse die ze declareert, niets hoeft te weten over de actuele objecttypen’! Misschien is dit oud nieuws, maar om er zeker van te zijn dat we over hetzelfde praten, volgt hier een eenvoudig voorbeeld waarin een polymorf type wordt gebruikt – denk aan een abstracte klasse Animal, met twee concrete implementaties, Dog en Cat. Programmeren naar een implementatie zou opleveren:
abstract een abst supertype (kan interfac racte klasse OF e zijn) een
Dog h = new Dog(); h.bark();
Animal
Maar programmeren naar een interface/supertype zou geven:
makeSound()
Animal animal = new Dog(); animal.makeSound();
concrete ies implementat Dog MakeSound() { bark(); } bark() { // blafgeluid }
12
421_01_nl.indd 12
Declaratie van de variabele ‘h’ als typ Dog (een concrete implementatie van e Animal) dwingt ons er toe om een con crete implementatie te coderen.
Cat makeSound() { meow(); } meow() { // miauwgeluid }
We weten dat het een Dog is, maar we kunnen nu de referentie naar animal polymorf gebruiken.
Nog beter dan de instantiatie van het subtype hard in de code te coderen (zoals new Dog()), is het om de concrete implementatie van het object at runtime toe te kennen: a = getAnimal(); a.makeSound();
We weten niet WAT het actuele subt van animal is ... we zorgen er alleen vooype dat het weet hoe het op makeSound() r moet reageren.
Hoofdstuk 1
30-04-2007 22:58:34
Inleiding tot Design Patterns
Implementatie van het eendengedrag We hebben nu twee interfaces FlyBehavior en QuackBehavior samen met hun bijbehorende klassen voor de implementatie van hun concreet gedrag:
rface en inte n e is r sse avio FlyBeh vliegende kla nieuwe e die all nteert. Alle ven alleen de impleme e klassen hoe plementeren. vliegend e fly() te im method
<>
FlyBehavior
FlyWithWings
<>
QuackBehavior quack()
fly()
fly() {
kwaakgedrag; En idem voor het rface die alleen we hebben een intek() bevat die we een methode quac ren. moeten implemente
FlyNoWay fly() {
Quack quack() {
Squeak
MuteQuack
quack() {
quack() {
// implementeert het
// doe niets - kan niet
// implementeert
// vliegen van eenden
// implementeert piepen
// vliegen!
// eendengekwaak
// van een badeend
}
}
}
Wann ee En dit is g e kwaak r er echt d e im p lem alle eende t wor n die niet entatie voor Dit is d dt. e k u nnen vlieg van het implementat e n . eenden vliegen van all ie met vle e ugels.
}
En we kunnen nieuw gedrag toevoegen zonder dat we de bestaande gedragsklassen hoeven te veranderen of de klassen van Duck die het vlieggedrag gebruiken hoeven te benaderen.
// kwaken! }
Wanneer er alleen gepiept wordt.
In dit ontwerp kunnen andere objecttypen ons gedrag voor vliegen en kwaken hergebruiken omdat dit gedrag niet langer binnen onze klassen van Duck is weggeborgen.
// doe niets - kan niet
Gekwaak zonder geluid.
het ijgen we IK r k e z ij w U Op deze l van HERGEBR ort bij o e h e voord de troep die ving. zonder ruik van overer het geb
Je bent hier 4
421_01_nl.indd 13
13
30-04-2007 22:58:35
Gedrag in een klasse
Er bestaan geen domme vragen
V:
V:
A:
A:
Moet ik altijd eerst mijn applicatie implementeren, daarna bekijken wat er verandert, en tot slot teruggaan en deze zaken scheiden en inkapselen?
Niet altijd; vaak anticipeer je al tijdens het ontwerpen van een applicatie op de aandachtsgebieden die kunnen variëren waarna je vervolgt met het inbouwen van de flexibiliteit om hier in de code rekening mee te houden. Je ontdekt dat de principes en patterns in iedere fase van de ontwikkelingslifecycle kunnen worden toegepast.
V: A:
Moeten we van Duck ook een interface maken?
Het is wel een beetje vreemd een klasse te hebben die alleen maar een gedrag vormt. Zijn klassen niet bedoeld om dingen voor te stellen? Moeten klassen niet zowel toestanden EN gedrag bezitten?
In een OO-systeem, ja. Daarin vertegenwoordigen klassen gewoonlijk dingen met toestanden (instantievariabelen) en methoden. In dit geval zijn die dingen gedrag. Maar zelfs een gedrag kan nog steeds een toestand en methoden hebben; het vlieggedrag kan bijvoorbeeld instantievariabelen kennen die de attributen voor het vlieggedrag voorstellen (vleugel, slagen per minuut, maximale hoogte en snelheid enzovoort).
In dit geval niet. Je zult nog zien dat wanneer we de hele boel bij elkaar hebben gevoegd, we baat hebben bij het feit dat Duck geen interface is en dat specifieke eenden, zoals de MallardDuck, gemeenschappellijke eigenschappen en methoden erven. Nu we de veranderlijke zaken uit de overerving van Duck hebben verwijderd, kunnen we zonder problemen de vruchten van deze structuur plukken.
Slijp je potlood
2
Kun je een klasse verzinnen die het gedrag van Quack kan gebruiken en toch geen eend is?
1) Maak een klasse FlyRocketPowered die de interface FlyBehavior implementeert.
Wat zou jij doen als in je in het nieuwe ontwerp vliegen met raketaandrijving aan de SimUDuck-applicatie moest toevoegen?
2) Een voorbeeld: een lokfluit voor eenden (een apparaat dat eendengeluiden maakt).
1
Antwoorden:
14
421_01_nl.indd 14
Hoofdstuk 1
30-04-2007 22:58:36
Inleiding tot Design Patterns
Integratie van het eendengedrag De sleutel is dat Duck het vlieg- en kwaakgedrag delegeert, in plaats van gebruik te maken van methoden voor het vliegen en kwaken, die gedefinieerd worden in de klasse Duck (of haar subklassen). Dat gaat als volgt: Eerst voegen we twee instantievariabelen toe aan de klasse Duck met de namen flyBehavior en quackBehavior. Deze worden gedeclareerd als het type van de interface (dus geen concreet implementatietype van een klasse). Ieder object eend zal deze variabelen polymorf invullen om naar het specifieke gedragstype at runtime te verwijzen (FlyWithWings, Squeak enzovoort).
1
Bovendien verwijderen we de methoden fly() en quack() uit de klasse Duck (en ook uit iedere subklasse), omdat we dit gedrag willen verplaatsen naar de klassen FlyBehavior en QuackBehavior. We vervangen fly() en quack() in de klasse Duck door twee vergelijkbare methoden, performFly() en performQuack(). Je zult nog zien hoe deze werken.
De gedraqsvariabelen worden gedeclareerd als het interfacetype voor dat gedrag.
Deze methoden vervangen fly() en quack().
2
Instantievariabelen bevatten de referentie naar bepaald gedrag at runtime. Duck FlyBehavior flyBehavior QuackBehavior quackBehavior performQuack() swim() display() performFly() // ANDERE eend-achtige methoden ...
Nu implementeren we performQuack(): public class Duck { QuackBehavior quackBehavior; // meer public void performQuack() { quackBehavior.quack(); } }
Vlie ggedrag Kw
aakge ag dr
Eendengedrag
ace s dat de interf t ie n aa rt ee er f Iedere Duck re implementeert. r io av eh QuackB kwaakgedrag af k t he f el z n va s In plaat het object Duc rt ee eg el d , en el door te hand het object dat dit gedrag aan wordt aangewezen. quackBehavior
Tamelijk eenvoudig, nietwaar? Om te kwaken staat Duck het object dat wordt aangewezen door quackBehavior, toe om voor hem te kwaken. In dit deel van de code kan het ons niet schelen welk soort object het is, ons interesseert alleen maar dat het weet hoe het quack() uitvoert! Je bent hier 4
421_01_nl.indd 15
15
30-04-2007 22:58:36
Het eendengedrag integreren
Nog meer integratie ... 3
Nu gaan we ons druk maken over hoe de instantievariabelen flyBehavior en quackBehavior ingesteld worden. We werpen een blik op de klasse MallardDuck: public class MallardDuck extends Duck { public MallardDuck() { quackBehavior = new Quack(); flyBehavior = new FlyWithWings(); }
Denk er aan dat MallardDuck de instantievariabelen quackBehavior en flyBehavior van de klasse Duck erft.
ck kt de klasse Qua ui br ge k uc D rd er . Wanne Een Malla af te handelen om het kwaken wordt aangeroepen, wordt performQuack elijkheid voor het kwaken de verantwoordn het object Quack en wordt gedelegeerd aa kt. er echt gekwaa s type voor FlyWithWings al kt ui br ge t he En FlyBehavior.
public void display() { System.out.println("Ik ben een echte wilde eend"); } }
Het kwaken van een MallardDuck is dus het echte levende gekwaak van een eend, en dus geen gepiep of geluidloos gekwaak.Wat is er aan de hand? Wordt er een instantie gemaakt van MallardDuck, dan initialiseert de constructor de instantievariabele quackBehavior die MallardDuck heeft geërfd, naar een nieuwe instantie van het type Quack (een concrete implementatie van de klasse QuackBehavior). En hetzelfde geldt voor het vlieggedrag van de eend: de constructor van MallardDuck initialiseert de instantievariabele flyBehavior met een instantie van het type FlyWithWings (een concrete implementatie van de klasse FlyBehavior).
16
421_01_nl.indd 16
Hoofdstuk 1
30-04-2007 22:58:37
Inleiding tot Design Patterns
Wacht eens even, zei je niet dat we NIET naar een implementatie mochten programmeren? Maar wat doe je dan in die constructor? We maken daar een nieuwe instantie van een concrete implementatie van de klasse Quack!
Goed punt, dat doen we inderdaad ... voor het moment. Later in dit boek beschikken we over meer patterns in onze toolbox waarmee we dit kunnen verhelpen. Let er overigens wel op dat we het gedrag via concrete klassen instellen (door de instantiatie van een gedragsklasse als Quack of FlyWithWings en er vervolgens onze referentievariabelen voor het gedrag aan toe te kennen), maar we kunnen dit in runtime eenvoudig wijzigen. We beschikken hier over veel flexibiliteit, maar we leveren slecht werk door de instantievariabelen op een flexibele wijze te initialiseren. Maar denk eens na, aangezien quackBehavior een instantievariabele van het type interface is, kunnen we (met de magie van polymorfisme), dynamisch at runtime een andere QuackBehavior-implementatie toekennen. Neem eens even de tijd om te bekijken hoe jij een eend zou implementerem zodat zijn gedrag at runtime kan veranderen. (Enkele pagina’s verder vind je daar de code voor.)
Je bent hier 4
421_01_nl.indd 17
17
30-04-2007 22:58:38
Het gedrag van de eend testen
De code voor Duck testen 1
Typ en compileer de volgende klasse Duck (Duck.java), en tevens de klasse MallardDuck die twee pagina’s terug staat (MallardDuck.java). public abstract class Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior; public Duck() { } public void performFly() { flyBehavior.fly(); }
ntievariabelen Declareer twee refere ‘gedrag’. Alle van het interfacetype dezelfde subklassen van Duck (in package) erven deze.
Delegeren aan de gedragsklasse.
public void performQuack() { quackBehavior.quack(); } public void swim() { System.out.println("Alle eenden drijven, ook lokeenden!"); } } 2
Typ en compileer de interface FlyBehavior (FlyBehavior. java) evenals de twee implementatiegedragsklassen (FlyWithWings.java en FlyNoWay.java). public interface FlyBehavior { public void fly(); }
De interface die alle vliegende gedragsklassen implementeert.
public class FlyWithWings implements FlyBehavior { gpublic void fly() { entatie van vlie em pl im e D System.out.println("Ik vlieg!!"); en die gedrag voor eendn ... } KUNNEN vliege }
public class FlyNoWay implements FlyBehavior { public void fly() { System.out.println("Ik kan niet vliegen"); } }
18
421_01_nl.indd 18
De implementati voor eenden die e van vlieggedrag (zoals badeendenNIET vliegen en lokeenden).
Hoofdstuk 1
30-04-2007 22:58:38
Inleiding tot Design Patterns
De code voor Duck testen, ver volg 3
Typ en compileer de interface QuackBehavior (QuackBehavior.java) evenals de implementatie voor de drie gedragsklassen (Quack.java, MuteQuack.java en Squeak.java). public interface QuackBehavior { public void quack(); } public class Quack implements QuackBehavior { public void quack() { System.out.println("Kwaak"); } } public class MuteQuack implements QuackBehavior { public void quack() { System.out.println("<<Stilte>>"); } } public class Squeak implements QuackBehavior { public void quack() { System.out.println("Piep"); } }
4
Type en compileer de testklasse (MiniDuckSimulator. java). public class MiniDuckSimulator { public static void main(String[] args) { Duck mallard = new MallardDuck(); mallard.performQuack(); mallard.performFly(); } }
5
Draai de code File Edit Window Help Yabadabadoo
%java MiniDuckSimulator
ardDuck geërfde Aanroep van de van Mall, die dit vervolgens methode performQuack t QuackBehavior (d.w.z. delegeert aan het objec van Duck geërfde roept quack() aan via deior). referentie quackBehav
Vervolgens doen we hetzelfde met de van MallardDuck geërfde methode performFly().
Kwaak Ik vlieg!! Je bent hier 4
421_01_nl.indd 19
19
30-04-2007 22:58:38
Eenden met dynamisch gedrag
Gedrag dynamisch instellen Wat jammer dat we geen gebruikmaken van al dat dynamische talent dat we in onze eenden hebben aangebracht. Stel je voor dat we het eendengedrag via een setmethode in de subklassen van Duck konden instellen in plaats van dit te initaliseren in de constructor van Duck. 1
Voeg twee nieuwe methoden toe aan de klasse Duck: public void set FlyBehavior(FlyBehavior fb) { flyBehavior = fb; }
Duck FlyBehavior flyBehavior; QuackBehavior quackBehavior;
public void setQuackBehavior(QuackBehavior qb) { quackBehavior = qb; }
swim() display() performQuack() performFly() setFlyBehavior() setQuackBehavior()
We kunnen deze methoden iedere keer aanroepen wanneer we het gedrag van de eenden vliegensvlug willen veranderen. Opmerking redacteur: weg
2
// ANDERE eend-achtige methoden ...
met dat flauwe woordgrapje
Maak een nieuw type Duck (ModelDuck.java). public class ModelDuck extends Duck { public ModelDuck() { flyBehavior = new FlyNoWay(); quackBehavior = new Quack(); }
op de int zijn leven eg te eg b nd ee el od om w Onze m er een manier grond ... zond vliegen.
public void display() { System.out.println("Ik ben een modeleend."); } }
3
Maak een nieuw type FlyBehavior (FlyRocketPowered.java).
Dit klopt, we creëren vlieggedrag voor raketaandrijving.
public class FlyRocketPowered implements FlyBehavior { public void fly() { System.out.println("Ik vlieg met raketaandrijving!"); } }
20
421_01_nl.indd 20
Hoofdstuk 1
30-04-2007 22:58:39
Inleiding tot Design Patterns
4
Verander de testklasse (MiniDuckSimulator. java), voeg de ModelDuck toe en maak ModelDuck geschikt voor raketaandrijving.
public class MiniDuckSimulator { public static void main(String[] args) { Duck mallard = new MallardDuck(); mallard.performQuack(); mallard.performFly();
voor
Fly() oep van performr-object nr aa te rs ee e D het flyBehavio delegeert naar uctor van ModelDuck is dat in de constr geval een instantie van ingesteld, in dit FlyNoWay.
Duck model = new ModelDuck(); model.performFly(); model.setFlyBehavior(new FlyRocketPowered()); model.performFly(); } }
5
Als het werkt, heeft de mo vlieggedrag dynamisch veranddeleend zijn NIET DOEN als de implementerd! Je kunt dat klasse Duck was opgesloten. atie binnen de
Hier wordt de setmethode gedrag dat het model geërfvoor het aangeroepen, en ... voilà! Het d heeft kan ineens met raketaandrij model ving vliegen!
Start de simulator! File Edit Window Help Yabadabadoo
%java MiniDuckSimulator Kwaak Ik vlieg!! Ik kan niet vliegen.
na
Ik vlieg met raketaandrijving.
Om het gedrag van een eend at runtime te veranderen, hoef je alleen maar de setmethode voor dat gedrag van de eend aan te roepen. Je bent hier 4
421_01_nl.indd 21
21
30-04-2007 22:58:39
Resumé
Resumé over ingekapseld gedrag Nu we in het diepe zijn gedoken bij het ontwerpen van de eendensimulator, wordt het tijd om wat adem te halen en het geheel te overzien. Hierna staat de volledige opnieuw bewerkte klassenstructuur. Hierin staat alles dat je zou verwachten: eenden die erven van Duck, vlieggedrag geïmplementeerd door FlyBehavior en kwaakgedrag geïmplementeerd door QuackBehavior. Merk op dat we in het begin de dingen een beetje anders hebben beschreven. In plaats van het eendengedrag te beschouwen als een verzameling gedragingen, bekijken we dit nu als een familie algoritmen. Bedenk dat in het ontwerp van SimUDuck de algoritmen de dingen voorstellen die een eend kan doen (kwaken en vliegen), maar we hadden dezelfde techniek net zo goed kunnen gebruiken voor een verzameling klassen voor de berekening van de inkomstenbelasting voor ieder land van de EU. Besteed veel zorg aan de relaties tussen de klassen. Je kunt het beste een potlood pakken en de juiste relatie noteren (IS-EEN, HEEFT EEN en IMPLEMENTEERT) bij iedere pijl in het klassendiagram.
De client gebruikt een ingekapselde familie algoritmen voor zowel het vliegen als het kwaken. Client
Ingekapseld vlieggedrag
re an iede Denk a eling verzam ingen als gedrag n familie aan ee men. algorit
<>
FlyBehavior fly()
Duck FlyWithWings
FlyBehavior flyBehavior QuackBehavior quackBehavior
FlyNoWay
fly() {
fly() {
// implementeert vliegende
swim()
// niets doen - kan niet
// eenden
display()
// vliegen
}
performQuack()
}
performFly()
Ingekapseld kwaakgedrag
setFlyBehavior() setQuackBehavior()
<>
// ANDERE eend-achtige methoden ...
QuackBehavior quack
MallardDuck
RedheadDuck
RubberDuck
DecoyDuck
display() {
display() {
display() {
display() {
// lijkt op een wilde
// lijkt op een
// lijkt op een badeend}
// lijkt op een lokeend}
// eend }
// roodkuifeend }
Quack quack() {
}
Squeak quack() {
// implementiert
// implementeert
// eendengekwaak
// gepiep badeend }
MuteQuack quack() { // niets doen – kan niet // kwaken! }
en aging n r d e g ij Dezeritmen’ zr. ‘algo isselbaa uitw 22
421_01_nl.indd 22
Hoofdstuk 1
30-04-2007 22:58:41
Inleiding tot Design Patterns
HEEFT-EEN is soms beter dan IS-EEN De relatie HEEFT-EEN is een interessante: iedere eend heeft een FlyBehavior en een QuackBehavior waaraan de eend het vliegen en kwaken delegeert. Wanneer je twee klassen als deze samenvoegt, dan gebruik je compositie. In plaats van het gedrag te erven, verkrijgt de eend zijn gedrag uit het (juiste) gedragsobject waarmee hij is samengesteld. Dit is een belangrijke techniek; we gebruiken feitelijk ons derde ontwerpprincipe:
Meester en leerling Meester: Sprinkhaan, vertel eens wat je over de objectgeoriënteerde aanpak hebt geleerd. Leerling: Ik heb geleerd dat de belofte van objectoriëntatie hergebruik is.
Ontwerpprincipe
Meester: Ga verder Sprinkhaan ...
Geef aan compositie de voorkeur boven overerving.
Leerling: Meester, via overerving kunnen alle goede dingen opnieuw gebruikt worden, waardoor de ontwikkeltijd drastisch omlaag gaat alsof we bamboe maaien in de bossen.
Je hebt gezien dat het creëren van systemen via compositie meer flexibiliteit geeft. Niet alleen kun je zo een familie algoritmen inkapselen, maar je kunt zo tevens het gedrag at runtime veranderen, zolang het object dat je samenstelt de correcte interface voor het gedrag implementeert. Compositie wordt in veel design patterns toegepast en je zult nog veel meer over de voordelen en nadelen ervan in dit boek ontmoeten.
Hersenkraker Een lokfluit is een apparaat dat jagers gebruiken om eendengekwaak te imiteren. Hoe zou jij je eigen lokfluit implementeren die niet van de klasse Duck erft?
Meester: Sprinkhaan, wordt er meer tijd aan de code besteed voor of nadat de ontwikkeling gereed is? Leerling: Het antwoord is nadat Meester. We besteden altijd meer tijd aan onderhoud en aanpassingen van de software dan tijdens de oorspronkelijke ontwikkeling. Meester: Dus Sprinkhaan, moeten we dan meer tijd besteden aan hergebruik dan aan onderhoudbaarheid en uitbreidbaarheid? Leerling: Meester, ik denk het wel. Meester: Ik zie dat je nog veel moet leren. Ik zou graag willen dat je verder over overerving gaat mediteren. Je hebt gezien dat overerving haar problemen kent en dat er andere manieren voor hergebruik bestaan.
Je bent hier 4
421_01_nl.indd 23
23
30-04-2007 22:58:42
Het strategy pattern
Over Design Patterns gesproken ...
1.
Gefeliciteerd met je eerste pattern!
Je hebt zojuist je eerste design pattern toegepast – het STRATEGY pattern. Inderdaad, je hebt het Strategy Pattern gebruikt om de SimUDuck-applicatie om te bouwen. Dankzij dit pattern is de simulator geschikt voor alle veranderingen die tijdens een volgende businesstrip kunnen worden uitgebroed. Nu je de lange weg naar het toepassen ervan hebt afgelegd, volgt hier de formele definitie van dit pattern:
Het Strategy Pattern definieert een familie algoritmen, isoleert ze en maakt ze uitwisselbaar. Strategy maakt het mogelijk om het algoritme los van de client die deze gebruikt, te veranderen.
24
421_01_nl.indd 24
e efiniti moet d E Z ers k DE nden Gebrui er je je vrie idsmedewerk wanne ren en bele impone beïnvloeden. moet
Hoofdstuk 1
30-04-2007 22:58:42
Inleiding tot Design Patterns
Design Puzzel Hierna staan een aantal klassen en interfaces voor een Action-Adventure. Er zijn klassen voor spelfiguren en klassen voor het gedrag van wapens. Iedere spelfiguur kan slechts een enkel wapen tegelijk gebruiken, maar kan wel op ieder moment tijdens het spel van wapen wisselen. Aan jou de taak om de boel op orde brengen ... (De antwoorden staan aan het einde van dit hoofdstuk.)
Jouw taak: 1 Arrangeer de klassen. 1. 2 2. Identificeer een abstracte klasse, een interface en acht klassen. 3 3. Teken pijlen tussen de klassen.
a. Teken een dergelijke pijl voor overerving (‘extends’). b. Teken een dergelijke pijl voor een interface (‘implements’). c. Teken deze pijl voor een ‘HEEFT-EEN’-relatie. 4 Voeg in de juiste klasse de methode setWeapon() in. 4.
Character WeaponBehavior weapon;
BowAndArrowBehavior
KnifeBehavior
fight();
useWeapon() { // implementeert met pijl en boog schieten }
useWeapon() { // implementeert steken met een mes }
<>
Queen
WeaponBehavior
fight() { ... }
useWeapon();
King fight() { ... }
Troll fight() { ... }
Knight fight() { ... }
AxeBehavior useWeapon() { // implementeert met een bijl hakken
SwordBehavior useWeapon() { // implementeert met een zwaard zwaaien }
setWeapon(WeaponBehavior w) { this.weapon = w; }
Je bent hier 4
421_01_nl.indd 25
25
30-04-2007 22:58:43
Dinergesprek
Tijdens een etentje opgevangen ... Alice Ik wil graag frites met ketchup en mayonaise, chocoladeen vanille-ijs, een gegrilde kaas-baconsandwich, een tonijnsalade op toast, een bananensplit met ijs en klein gesneden bananen en een kop koffie met melk en twee suikerklontjes, ... oh, en leg een hamburger op de grill!
Flo Eenmaal frites rood/ wit, eenmaal bruin/wit, een Jack Benny, een radio, een huisboot, een kinderkoffie en verschroei er een!
Wat is het verschil tussen deze twee bestellingen? Geen! Ze bestellen beiden hetzelfde. Maar Alice gebruikt tweemaal zoveel woorden en stelt daarbij het geduld van de morrige kok op de proef. Wat heeft Flo dat Alice niet heeft? Een gemeenschappelijk vocabulaire met de kok. Het is zo niet alleen makkelijker om met de kok te communiceren, maar zo hoeft de kok ook minder te onthouden omdat hij de diner patterns al in zijn hoofd heeft zitten. Design Patterns zorgen voor een gemeenschappelijke vocabulaire met andere ontwikkelaars. Ken je het vocabulaire, dan kun je makkelijker met andere ontwikkelaars communiceren en andere ontwikkelaars die de patterns nog niet kennen, inspireren om ze te leren. Het verhoogt ook je conceptueel niveau over architecturen omdat je op patternniveau denkt en niet op het platvloerse objectniveau.
26
421_01_nl.indd 26
Hoofdstuk 1
30-04-2007 22:58:44
Inleiding tot Design Patterns
Op kantoor opgevangen ... Ik heb een broadcast klasse gemaakt. Die houdt alle luisterende objecten bij en stuurt een boodschap naar iedere luisteraar wanneer er nieuwe gegevens zijn. Heel cool is dat de luisteraars zich op ieder moment bij de broadcast kunnen aansluiten of zichzelf weer kunnen verwijderen. Het is werkelijk heel dynamisch en los gekoppeld!
Hersenkraker
Rick Kun je nog een ander gemeenschappelijk vocabulaire verzinnen behalve het voor OO-ontwerpen en het bestellen van eten? (Aanwijzing: denk eens aan automonteurs, timmerlui, chefkoks en luchtverkeersleiders) Welke kwaliteiten worden samen met de taal gecommuniceerd? Kun je aspecten van het OO-ontwerp bedenken die samen met de namen van patterns gecommuniceerd worden? Welke kwaliteiten worden samen met de naam ‘Strategy Pattern’ gecommuniceerd?
Rick, waarom zeg je niet gewoon dat je een Observer Pattern gebruikt?
Precies. Als je met patterns communiceert, dan weten andere ontwikkelaars precies welk ontwerp je beschrijft. Maar voorkom patternkoorts ... Dat heb je als je patterns gaat gebruiken voor Hallo Wereld ...
Je bent hier 4
421_01_nl.indd 27
27
30-04-2007 22:58:45
Gemeenschappelijk vocabulaire
De kracht van een gemeenschappelijk patternvocabulaire Wanneer je communiceert via patterns, dan doe je meer dan het delen van vaktaal.
Gemeenschappelijke patternvocabulaires zijn KRACHTIG. Als je via patterns met andere ontwikkelaars of je team communiceert, dan praat je niet alleen over een patternnaam, maar ook over een hele verzameling kwaliteiten, karakteristieken en beperkingen die het pattern vertegenwoordigt.
ern om tegy patt den te a r t s t e h een iken ‘We gebru se gedrag van onzent dat het r e e k het div eren.’ Dat bete in zijn eigen implement rag is ingekapseld elijk veranderd eendenged g klassen die makk , zo nodig in verzamelin eid kunnen worden en uitgebr Met patterns kun je meer zeggen met minder. Wanruntime. neer je een pattern in een beschrijving gebruikt, dan weten andere ontwikkelaars snel en precies wat voor een ontwerp je in gedachten hebt. Door op patternniveau te praten blijf je langer bij ‘het ontwerp’. Door met behulp van patterns over softwaresystemen te praten, blijft de discussie langer op het ontwerpniveau, zonder dat je moet afdalen naar de technische details van de implementatie van objecten en klassen. Gemeenschappelijke vocabulaires brengen vaart in je ontwikkelteam. Een team dat vertrouwd is met design patterns, schiet harder op en er is minder ruimte voor misverstanden. Gemeenschappelijke vocabulaires moedigen junior ontwikkelaars aan om zich sneller te ontwikkelen. Junior ontwikkelaars kijken tegen ervaren ontwikkelaars op. Wanneer senior ontwikkelaars design patterns gebruiken, zullen de junior ontwikkelaars deze ook willen leren. Zet dus een gemeenschap van patterngebruikers op in je organisatie.
28
421_01_nl.indd 28
je en heb en tot een g n i r e rd gad erpver nel degradeeetails? w t n o l s Hoevee oond die al plementatied w m e i g bij king van bespre
t met het delen van Wanneer je team beginringen in termen van ontwerpideeën en ervagemeenschap op van patterns, zet je een patterngebruikers.
Overweeg het opzetten van een studiegroep voor patterns binnen je organisatie, misschien word je tijdens het leerproces al betaald ... ;)
Hoofdstuk 1
30-04-2007 22:58:45
Inleiding tot Design Patterns
Hoe gebruik ik Design Patterns? We hebben allemaal wel eens met bibliotheken en frameworks van de plank gewerkt. We pakten ze op, schreven wat code rond hun API’s, compileerden deze in onze programma’s en hadden baat bij een hoop code die door anderen was geschreven. Denk eens aan de API van Java en alle functionaliteit die deze je geeft: netwerk, GUI, IO enzovoort. Bibliotheken en frameworks brengen je zo al een heel eind op weg naar een ontwikkelingsmodel waar je gewoon de componenten selecteert om ze in te pluggen. Maar... ze helpen ons niet om onze applicatie te structureren op een wijze die eenvoudig te begrijpen en te onderhouden is en genoeg flexibiliteit biedt. Hier komen Design Patterns om de hoek kijken. Design Patterns komen niet direct in de code terecht, ze komen eerst in je HERSENEN terecht. Wanneer je eenmaal een goed inzetbare kennis van patterns hebt verworven, kun je deze in je nieuwe ontwerpen toepassen en je oude code herbewerken wanneer je vindt dat deze is gedegradeerd tot een onwerkbare bak spaghetticode.
ENEN
edrag d vliegg Inkapsel rface>> <
Je HERS
r
FlyBehavio
fly
FlyNoWay ngs FlyWithWi
g aakgedra pseld kw Ingeka > <
swim() display() () performQuack
lten
QuakVerha
quaken()
performFly() havior() setQuackBe ior() setFlyBehav methoden eendachtige // ANDERE
MuteQuack Squeak Quack Decoy Duck
{ display() nd } een badee // lijkt op
8
8 int
e kt-Obj
}
OBSERVER
Object dat de toestand bevatt.
Su bje
quack() { – kan niet // niets doenn // kwkaen!
8 8
Dog objec
tt
ten
RedheradD
{ display() // lijkt opeen ifeend } // roodku
quack() { enteert // implem badeend // gepiep
quack() { enteert // implem aak} // eendengekw
{ display() d} een lokeen // lijkt op
8 t Duc k objec
automatische update/berichtgeving
Mo
Cat object
ct use obje
r trolle
Con
Beobachter
MVC
jke objec
uck
k
MallardDuc
{ display() een wilde // lijkt op // eend }
Duck
afhankeli
Rubber
kt
Een stapel Patterns
fly() { - kan niet // niets doen } // vliegen
fly() { enteert vliegen // implem // eenden
Duck flyBehavior FlyBehavior Behavior ior quack QuackBehav
Client
el
est Requ
Mod
View
Je met design uwde en patterns vernie e! verbeterde cod
Er bestaan geen domme vragen
V:
Als design patterns zo belangrijk zijn, waarom bouwt iemand dan geen bibliotheek van ze, zodat ik dat niet hoef te doen?
A:
Design patterns zijn van een hoger niveau dan bibliotheken. Design patterns zeggen ons hoe we klassen en objecten kunnen structureren om bepaalde problemen op te lossen en het is ons werk om deze ontwerpen aan een bepaalde applicatie aan te passen.
V:
die rond design patterns gestructureerd zijn.
A:
Bestaan er geen bibliotheken met design patterns?
Vormen bibliotheken en frameworks ook geen design patterns?
Frameworks en bibliotheken zijn geen design patterns; ze zorgen voor bepaalde implementaties die we aan onze code koppelen. Bibliotheken en frameworks maken soms echter wel gebruik van design patterns in hun implementatie. Dat is prachtig omdat, wanneer je design patterns eenmaal begrijpt, je ook sneller de API‘s begrijpt
V:
A:
Nee, maar je zult later pattern catalogi tegenkomen waarin patterns worden vermeld die je in je applicaties kunt gebruiken.
Je bent hier 4
421_01_nl.indd 29
29
30-04-2007 22:58:46
Waarom design patterns?
Patterns zijn niets anders dan het gebruik van de ontwerpprincipes van OO. Sprinkhaan, dit is een wijdverbreid misverstand, maar het is wel wat subtieler. Je zult nog veel moeten leren ...
Sceptische ont wikkelaar
Pattern vriendelijke goeroe
Ontwikkelaar: Oké, maar is dit niet gewoon allemaal een goed objectgeoriënteerd ontwerp? Ik bedoel, zolang ik de regels voor inkapseling volg en kennis heb van abstractie, polymorfisme en overerving, moet ik dan werkelijk over Design Patterns nadenken? Het is eigenlijk toch tamelijk eenvoudig? Heb ik daarom niet al die OOcursussen gevolgd? Ik denk dat Design Patterns heel nuttig zijn voor mensen die niets weten over een goed OO-ontwerp. Goeroe: Aha, dit is het werkelijke misverstand over objectgeoriënteerde ontwikkeling: dat we denken dat wanneer we de basis van OO kennen, we ook automatisch goede, herbruikbare en onderhoudbare systemen bouwen. Ontwikkelaar: Niet dan? Goeroe: Nee, de praktijk leert dat het bouwen van OO-systemen met deze eigenschappen niet altijd voor de hand ligt en alleen gebeurt door noeste arbeid. Ontwikkelaar: Ik denk dat ik het begrijp. Deze minder voor de hand liggende manieren om objectgeoriënteerde systemen te bouwen zijn verzameld ... Goeroe: ... juist, in een verzameling patterns die we Design Patterns noemen. Ontwikkelaar: Dus wanneer ik de patterns ken, kan ik het vervelende werk dan overslaan en meteen ontwerpen maken die het altijd doen? Goeroe: Ja tot op zekere hoogte wel, maar ontwerpen is een kunst. Er zullen altijd compromissen zijn. Maar volg je weloverwogen en langdurig geteste design patterns, dan ben je goed bezig.
30
421_01_nl.indd 30
Hoofdstuk 1
30-04-2007 22:58:49
Inleiding tot Design Patterns
Denk eraan, kennis van concepten als abstractie, overerving en polymorfisme maken je nog niet tot een goede objectgeoriënteerde ontwerper. Een ontwerpgoeroe bekijkt hoe je flexibele ontwerpen kunt maken die onderhoudbaar zijn en met verandering kunnen omgaan.
Ontwikkelaar: En wat moet ik doen als ik geen pattern kan vinden? Goeroe: Er bestaan enkele objectgeoriënteerde principes die de basis vormen voor de patterns en wanneer je deze kent, kunnen ze je helpen een probleem aan te pakken waarvoor je geen passend pattern kunt vinden. Ontwikkelaar: Principes? U bedoelt andere dan abstractie, inkapseling, en ... Goeroe: Ja. Een van de geheimen voor het maken van onderhoudbare OO-systemen is te bedenken hoe deze in de toekomst kunnen veranderen en deze principes gaan over dat soort onderwerp.
Je bent hier 4
421_01_nl.indd 31
31
30-04-2007 22:58:49
Jouw design toolbox
Gereedschappen voor je ont werp-toolbox Je hebt het eerste hoofdstuk nu bijna gehad! Je hebt al enkele gereedschappen in je OO-toolbox gelegd; voor we naar hoofdstuk 2 gaan, sommen we ze nog even op.
e de uit dat j ent en weet n a v r e n We gaa selen van OO k lassen basisbegin lymorfisme bij k jkt op hoe je po, hoe overerving lie inkapseling gebruikt contract en ho ker over design by el je jezelf onze t je Javawerkt.Vo en, pak dan eers door deze zak de kast, neem ditlgende boek uit n pas naar het vo en ga da k. hoofdstu
OO-Basics
Abstractie Inkapseling e Polymorfism Overerving
s
OO-Principe
verandert. Isoleer wat oversitie boven o p m o c s ie k Ver erving. raar een inte menn r e e m m a r ple Prog naar een im face en niet tatie.
er onderweg nad We zullen dit k nog het een en bekijken en oo lijst toevoegen. ander aan de
s OO-Patteierernt een familie algoritzedefin
ak de
Strategyl –ieder algoritme in en malga oritmen
men, kapse r. Strategy laat toe eze gebruiuitwisselbaa jk van de clients die d onafhankeli nderen. ken, te vera
Eén afgehandeld, maar 32
421_01_nl.indd 32
Denk er bij het lezen van dit boek steeds aan hoe patterns op de basisbeginselen en principes van OO berusten.
Punt voor punt
ß
Je bent geen goede OO-ontwerper als je alleen de basis van OO kent.
ß
Goede OO-ontwerpen zijn herbruikbaar, uitbreidbaar en onderhoudbaar.
ß
Patterns tonen hoe je systemen met goede OO-ontwerpkwaliteiten kunt bouwen.
ß
Patterns zijn bewezen objectgeoriënteerde ervaring.
ß
Patterns leveren geen code op, ze geven algemene oplossingen voor ontwerpproblemen. Deze kun je voor een bepaalde applicatie toepassen.
ß
Patterns worden niet uitgevonden, ze worden ontdekt.
ß
De meeste patterns en principes gaan over het aanbrengen van veranderingen in software.
ß
De meeste patterns maken het mogelijk om systeemdelen onafhankelijk van andere delen te veranderen.
ß
We proberen vaak het veranderlijke systeemdeel te bepalen en dit in te kapselen.
ß
Patterns zorgen voor een gemeenschappelijk taalgebruik dat de communicatie met andere ontwikkelaars kan optimaliseren.
nog vele te gaan!
Hoofdstuk 1
30-04-2007 22:58:50
Inleiding tot Design Patterns
De oplossing van de Design Puzzel Character is de abstracte klasse voor de verschillende spelfiguren (King, Queen, Knight en Troll) terwijl Weapon de interface is naar de implementatie van alle wapens. Dus de feitelijke spelfiguren en wapens zijn de concrete klassen. Om van wapen te wisselen roept iedere spelfiguur de methode setWeapon aan die gedefinieerd is in de superklasse Character. Tijdens een gevecht wordt de methode useWeapon aangeroepen voor het gebruikte wapen dat voor een spelfiguur is ingesteld om zo veel mogelijk lichamelijke schade aan andere spelfiguren toe te brengen.
abstract Character WeaponBehavior weapon; fight(); setWeapon(WeaponBehavior w) { this.weapon = w; }
King fight() { ... }
Knight fight() { ... }
Troll
Queen fight() { ... }
Een spelkarakter HEEFTEEN WeaponBehavior.
fight() { ... }
<> WeaponBehavior useWeapon();
SwordBehavior useWeapon() { // implementeert zwaaien met een zwaard
ct de DER obje kan IE t a d p o r r havio Let e en WeaponBe interface eren. Bijvoorbeeld e of een a t t n s e a p implem een tube tand paperclip, e goudvis. gemuteerd
BowAndArrowBehavior useWeapon() { // implementeert schieten met pijl en boog }
KnifeBehavior
useWeapon() { // implementeert steken met een mes }
AxeBehavior
useWeapon() { // implementeert hakken met een bijl }
Je bent hier 4
421_01_nl.indd 33
33
30-04-2007 22:58:50
Antwoorden
Antwoorden Slijp je potlood Welke van de volgende alternatieven zijn nadelen aan het gebruik van overerving om voor het gedrag van een eend te zorgen? (Meerdere alternatieven zijn mogelijk.)
❏ A. Code wordt in de subklassen gedupliceerd. ❏ B. Gedragsveranderingen at runtime zijn lastig. ❏ C. We kunnen eenden niet laten dansen.
Slijp je potlood
❏ D. Het is moeilijk om het gedrag van alle eenden te kennen. ❏ E. Eenden kunnen niet tegelijk vliegen en kwaken. ❏ F. Veranderingen kunnen onbedoeld andere eenden beïnvloeden.
Er zijn een hoop oorzaken die tot veranderingen kunnen leiden. Maak een lijst met redenen waarom je de code van applicaties moest veranderen (we hebben er maar vast een paar genoemd om je op weg te helpen).
Mijn klanten of gebruikers vonden dat ze iets anders wilden, of ze wilden nieuwe functionaliteit. Mijn bedrijf besloot op een andere databaseleverancier over te stappen en koopt ook zijn gegevens van een andere leverancier die een verschillend formaat gebruikt. Oei! Technologieën veranderen, en we moeten onze code updaten om nieuwe protocollen te kunnen gebruiken. We hebben nu zoveel over het te bouwen systeem geleerd dat we graag een stap terug willen doen om de zaken een beetje beter aan te pakken.
34
421_01_nl.indd 34
Hoofdstuk 1
30-04-2007 22:58:52