Övningar
Övningar
Under övningspassen kommer ni arbeta i grupp med att lösa problem, som vi sen diskuterar i grupp. Syftet är att väcka tankar och funderingar kring de principer och mönster vi sen diskuterar på efterföljande föreläsning.
Övningstillfällena är viktigare än föreläsningarna för ditt lärande!
Vid övningspassen är i olika salar, tiderna och sal hittar ni på TimeEdit.
Övningsuppgifter
Övningarna kommer länkas här under kursens gång.
Övning 1-1 DrawPolygons
-
Ladda hem
DrawPolygons.java
från kursens hemsida -
Diskutera koden i grupp:
- Vilka förändringar av koden skulle göra den bättre?
- … vad betyder ”bättre”?
-
Förbättra koden enligt de förändringar ni kommer fram till
-
För mer utmaning:
- Lägg till möjligheten att klicka ut nya polygoner
- Hur bör denna förändring implementeras?
Övning 1-2 UML
-
Utgå från
DrawPolygons.java
(från övning 1-1). - Syfte: refactor till en design som följer OCP och öva UML.
-
Utför följande refactoring steps:
-
Skapa en ny class
Polygon
som håller ihopname
ochcenterPoint
. Skapa en konstruktorPolygon(String, Point)
. Använd iDrawPolygons
. -
Ge
Polygon
enpaint
-metod. Flytta relevant funktionalitet frånDrawPolygons.paint
. -
Förenkla
if
-satsen iPolygon.paint
genom att lyfta ut relevant funktionalitet i tre separata metoder:paintSquare
,paintTriangle
ochpaintRectangle
. -
Skapa tre subklasser till
Polygon
:Square
,Triangle
ochRectangle
. Ta bort alla aspekter avname
. Ge varje subklass rättpaint
-metod. Förenklapaint
-metoden iDrawPolygons
. -
Gör
Polygon
abstract. Gör constructorPolygon(Point)
privat. Skapa en publik konstruktorPolygon(int, int)
som anroparPolygon(Point)
, och förenkla iDrawPolygons
.
-
Skapa en ny class
- För varje delsteg, rita upp det klassdiagram (UML) som beskriver systemet.
- Skriv JavaDoc för alla klasser och metoder
- För extra utmaning: Fundera över andra sätt (än att lägga till fler sorters polygoner) som programmet kan komma att utökas eller förändras. Hur kan ni planera för dessa?
Övning 2-1: Dynamic binding
Kod (zippat IntelliJ-projekt).
- Utgå från resultatet från föregående övning (kan laddas ner färdig från hemsidan om ni inte har er egen).
-
Implementera
toString()
-metoder för Polygon och alla dess subclasses. Det viktiga är att de skriver ut vilken typ objektet i fråga har. -
Lägg till en metod
overlaps(Polygon p) : boolean
iPolygon
. Vi låtsas att denna metod beräknar huruvida två polygoner överlappar. För nu, låt den alltid returneratrue
– men skriva ut (tillSystem.out
) tillräcklig information för att kunna avgöra vilken metod som anropas, och vilken typ operanderna har:-
E.g. ”Polygon.overlaps: Square vs Triangle” – använd
toString()
.
-
E.g. ”Polygon.overlaps: Square vs Triangle” – använd
-
Skriv en
main
-funktion som skapar några olika polygoner av olika slag och jämför med varandra med overlaps. Stämmer utskrifterna med vad ni förväntar dig? Kan ni förklara vad som händer i termer av statisk och dynamisk typ? -
Lägg till en overloaded metod
overlaps(Triangle t) : boolean
iTriangle
, och motsvarande i övriga subclasses. Låt dessa skriva ut liknande information så ni kan se vilken metod som anropas. Testa igen att jämföra olika polygoner. Stämmer det med vad ni förväntar er? -
Skapa ytterligare en polygon (av någon specifik dynamisk typ), men låt denna hållas av en variabel av statisk typ
Polygon
. Testa att jämföra även denna, med sig själv och med övriga polygoner med mer specifik statisk typ. Testa den både som det objekt som overlap anropas på, och som argument. Kan ni förutsäga vilka utskrifter ni kommer få? -
Overridea metoden
overlaps(Polygon p) : boolean
i alla subclasses, med en särskiljande utskrift. Kör testerna igen. Kan ni förutsäga vilka utskrifter ni kommer få?
Övning 2-2: Subclassing vs Delegation
Implementera följande tre scenarier, dels med subclassing, dels med delegering:
-
En plattform för att spela RPGs har ett koncept av användare (
User
). I olika spel kan en användare vara antingen spelare (Player
) eller spelledare (GameMaster
). Det finns gemensam kod (userMethod()
), och kod som är specifik för de olika rollerna (playerMethod()
ochgameMasterMethod()
). -
En kvadrat är bara en speciell sorts rektangel. Implementera en
Rectangle
(oberoende av tidigare uppgifter) med höjd och bredd, med setter och getter methods för båda, och en kombineradsetSize(int h, int w)
. Implementera en Square med den extra (overloaded) metodensetSize(int w)
. -
En skrivare kan använda antingen laser eller bläck. Implementera en
Printer
med metodenprint()
. Implementera enLaserPrinter
med metodchangeToner()
, och enInkPrinter
med metodchangeInk()
.
Diskutera för varje scenario för- och nackdelar med subclassing vs delegering. Bör subclassing användas?
- Rita upp UML-diagrammen för varje fall.
- Försök hitta generella principer för när subclassing är lämpligt.
Övning 3-1: Subtyping och Variance
Utgå från DrawPolygons
som tidigare.
-
Skapa en ny class
TestSubtyping
. -
Skapa några variabler av följande typer (med samma längd):
Polygon[]
,Triangle[]
,List<Polygon>
,List<Triangle>
. Testa följande, och se vad kompilatorn säger. Försök förutspå resultaten först:-
Vilken sorts objekt får ni lägga i respektive array eller lista?
Triangle
?Polygon
?Object
? - Mellan vilka av era variabler får ni lov att tilldela (dvs hela arrayer eller listor)?
-
Mellan vilka kan ni tilldela alla element från den ena till den andra (genom en
for
-loop)? -
Vilken typ av objekt kan ni plocka ut ur respektive array (
a[index]
) eller lista (l.get(index)
)? - Hjälper explicit casting vid något av ovanstående?
-
Vilken sorts objekt får ni lägga i respektive array eller lista?
-
Skapa två nya variabler av följande typer:
List<? extends Polygon>
,List<? super Polygon>
. Gör samma tester som ovan (med listorna från ovan). Förutspå resultaten innan ni testar. -
Skapa en metod
paintAll(Graphics,List<Polygon>) : void
som går igenom listan den får som argument och anroparpaint
-metoden på varje.- Vilka av ovanstående listor kan ni ge som argument till denna metod?
- Kan ni lägga till nya element i listan inuti metoden?
-
Ändra parametertypen för
paintAll
till först covariant och sen contravariant. Hur påverkar respektive alternativ? Förutspå resultaten! - Skapa en metod som tar två listor som argument, och flyttar alla element från den första till den andra. Vilka typer bör ni ge parametrarna för att göra metoden så användbar som möjligt?
- Kan ni göra samma sak för arrays? Hur skiljer det sig?
Övning 3-2: Generics
Börja från koden som finns att ladda ner här.
-
Färdigställ implementationen av klassen
Tuple
. Tanken är att den ska fungera som en tupel i e.g. Haskell eller Python. Den ska hålla två värden av (potentiellt) olika (godtycklig) typ, och ha metodernafst()
ochsnd()
för att returnera första respektive andra komponenten. -
Titta på koden för
AnimalShelter
med tillhörande kring-klasser. Kika särskilt påShelterError
. Hur kan vi tänka om, så att det inte är möjligt att sätta hundar i ett katthem, och vice versa, utan att förlora code reuse eller extensibility? Vi vill få ett statiskt felmeddelande, inte ett fel (exception) vid runtime. -
Skapa en
Lists
klass och implementera en statisk metodzip
som tar två listor (List<A>
ochList<B>
) som argument och returnerar en ny lista med tupel avA
ochB
(List<Tuple<A,B>>
). Resultat listan har samma längd som minsta argument lista. -
För utmaning: färdigställ implementationen av interfacet
Function
. Tanken är att det ska representera funktioner från argument av någon typT
till resultat av någon typR
. Interfacet ska ha metodernacompose
, som sätter ihop två funktioner av lämpliga argument- och retur-typer, samtapply
, som applicerar funktionen på ett lämpligt argument. Hur kan ni göra metoderna så polymorfa som möjligt? -
För extra utmaning: färdigställ implementationen av klassen
Either<A,B>
. Tanken är att den ska fungera som typenEither
i Haskell. Den ska representera antingen ett objekt av typenA
(annoteratLeft
) eller ett objekt av typenB
(annoteratRight
), och ha metodernaisLeft()
ochisRight
. Den ska också ha metodeneither
som tar två parametrar med typenFunction
(left
ochright
) och antingen exekverar left om objektet är en ‘left’ annars köra right. (Obs! Kräver kännedom om Lambdas!).isRight()
. -
För extra, extra utmaning: Ge en default-implementation av metoden
Function.compose
från ovan (Obs! Kräver kännedom om Lambdas!).
Övning 4-1: High cohesion, Low coupling
Börja från koden som finns att ladda ner här.
- Koden för övningen är en utökning av tidigare kod för att rita polygoner; nu kan polygonerna röra på sig. Den nya koden bryter mot alla tänkbara principer – ert jobb är att analysera och förbättra den.
- Börja med att rita ett UML-diagram av designen. Kan ni identifiera några specifika problem?
- Diskutera och fundera över hur en bättre design skulle kunna se ut. Rita ett UML-diagram av denna bättre design.
- Vilka refactoring-steg behöver ni genomföra för att uppnå er bättre design? Think before you code!
- Refaktorera koden – men inte förrän ni har en uttalad plan!
Övning 4-2: Separation of Concern
Börja från koden som finns att ladda ner här.
-
Vår gamla kod för
DrawPolygons
(oförändrad från förra veckan) ligger nu i ett pakettda551.polygons
. Det finns också ett till pakettda551.shapes
som ger en alternativ implementation av polygoner med mer funktionalitet. I förlängningen (inte idag) är det tänkt att vi ska byta ut vår naiva hantering mot den mer kraftfulla – men båda två har problem som behöver lösas först. -
På metod-nivå: Titta på metoderna i de olika klasserna, i båda paketen. Vilka metoder har ett väldefinierat och väl avgränsat ansvarsområde? Vilka metoder gör mer än en sak? Vilka metoder gör överlappande saker (kod-duplicering)?
- Tillämpa funktionell nedbrytning och refactoring för att stegvis lösa problemen.
-
På class-nivå: Titta på de olika klasserna, i båda paketen. Vilka klasser har ett väldefinierat och avgränsat ansvarsområde? Vilka klasser gör mer än en sak?
- De klasser som ni tycker gör en sak – ge en definition av vad denna enda sak är. Hur ”abstrakt” är er definition – dvs hur mycket av funktionaliteten täcker definitionen (inte)?
-
På paket-nivå (för mer utmaning): Paketet
tda551.shapes
är tänkt att vara en väl avgränsad (dock inte väl implementerad ännu) implementation av (Swing
-)polygoner. Fundera över vilka delar avtda551.polygons
som skulle behöva bytas ut mot detta paket. Vilka problem ser ni som ligger i vägen för ett sådant byte? Vad hade vi förutseende kunnat göra medtda551.polygons
för att göra ett sådant framtida byte enklare?- I enlighet med OCP: förutsäg förändring, och möjliggör extension istället för modification.
-
Fun quiz (orelaterat): I paketet
tda551.quiz
finns en classMystery
. Vad är dess syfte?
Övning 5-1: Factory Method
Börja från koden som finns att ladda ner här.
-
Vår gamla kod från förra veckan är uppdaterad enligt vad vi gjorde på förra föreläsningen: Vi har nu ett separat sub-paket
tda551.polygons.polygon
som innehåller data-representationen. Vi har inga beroenden från detta paket påDrawPolygons
som ligger utanför. Dock har vi ett beroende frånDrawPolygons
direkt på subklassernaTriangle
,Rectangle
ochSquare
, via konkreta anrop av dessas konstruktorer.DrawPolygons
har också fortfarande problem med att försöka göra för mycket – denna klass är inte uppdaterad helt. -
Koden för
tda551.shapes
är nu fullt uppdaterad enligt Separation of Concern. Allt gemensamt beteende för polygoner är lagt i en abstrakt superklassPolygon
(subklass till det ännu mer generellaShape
), och de specifika klassernaRectangle
ochTriangle
innehåller nu enbart det som skiljer dem åt. -
Introducera en factory-class, med Factory Methods för att skapa trianglar, rektanglar och kvadrater.
- Vilka argument behöver metoderna ta?
- Vilken returtyp vill vi ge de olika metoderna?
-
Vår nya factory bör bo i sub-paketet
tda551.polygons.polygon
. Varför? - Rita ett UML-diagram över vår nya design. Vilket gränssnitt (publika klasser och metoder) har sub-paketet nu?
- Kan du göra gränssnittet ännu mer abstrakt, dvs exponera ännu mindre av paketets konkreta interna implementation, utan att förlora funktionalitet?
-
Nu är det dags att fundera över hur vi skulle kunna byta representation av polygoner i vårt
DrawPolygons
-program, tilltda551.shapes
.-
Vilka refactoring-steg skulle behövas för att vi, när vi gör bytet, inte skulle behöva ändra i koden i
DrawPolygons
, mer än dess imports? -
Vilka komponenter behöver läggas till när vi gör bytet, för att inte behöva ändra i
tda551.shapes
? - Implementera förändringarna, och byt paket.
-
Vilka refactoring-steg skulle behövas för att vi, när vi gör bytet, inte skulle behöva ändra i koden i
Övning 5-2: Model-View-Controller
Börja från koden som finns att ladda ner här.
-
Vår gamla kod från förra veckan är uppdaterad enligt vad vi gjorde på föreläsningen i onsdags (färdigställt): Vårt sub-paket
tda551.polygons.polygon
har nu en fasad (Facade Pattern) som består avPolygonFactory
(Factory Method Pattern) och interfacetIPolygon
.DrawPolygons
beror endast på denna fasad, och inga paket-interna detaljer (det kan den inte eftersom dessa inte längre exponeras utanför paketet). -
Paketet
tda551.shapes
är oförändrat sen förra veckan. Detta paket följer inte våra principer, och exponerar fortfarande paket-interna detaljer. Eftersom vi ”låtsas” att detta är ett paket vi laddat ner från nätet och inte vill ändra i, eftersom vi vill kunna ta del av framtida uppdateringar och bug-fixar, så låter vi det vara så för nu. För extra övning kan ni dock omvandla även detta paket så att det exponerar ett väldefinierat och genomtänkt gränssnitt (men inte just nu, det är inte fokus för dagens övning). -
Vi har introducerat klasser och gränssnitt för att få det något annorlunda gränssnitt som
tda551.shapes
exponerar att fungera ihop medDrawPolygons
(Adapter Pattern). Dessa ligger i ett separat pakettda551.adapter
. Som uppföljning från förra veckan, läs och förstå hur vår adapter fungerar, och varför vi behöver använda oss av denna. Byt sen import iDrawPolygons
, från det gamla till det nya enligt instruktion i filen, och se att det ”bara funkar”. - Betrakta nu vår kod utifrån ett MVC-perspektiv. Vad är vår Model? Vad är vår View? Vad är vår Controller? Är dessa väl åtskiljda från varandra? (Ledande fråga – svaret är såklart nej.)
-
Den stora boven är klassen
DrawPolygons
, som har aspekter av alla tre benen av MVC i sig. LåtDrawPolygons
vara den klass som definierar vårt toppnivå-program, som innehåller metoden main, och initierar de ingående komponenterna.-
Skapa tre olika klasser:
-
PolygonModel
(vår Model, hanterar alla polygoner och deras tillstånd); -
PolygonViewer
(vår View, visar polygoner på skärmen); -
PolygonController
(vår Controller, styr animation och user input).
-
-
Flytta relevanta delar av den gamla
DrawPolygons
till var och en av dessa. - Fundera över hur de behöver kommunicera med varandra. Vem behöver bero på vem?
- Med de ändringar ni nu gjort – har vi nu en väl avgränsad uppdelning i enlighet med MVC? Svaret är inte helt självklart, på mer en ett sätt, oavsett hur ni löst det. Fundera över argument för och emot.
-
Skapa tre olika klasser:
-
Istället för att starta med en fix lista av polygoner, låt användaren klicka ut nya rektanglar. Titta på gränssnittet
java.awt.event.MouseListener
. -
För mer utmaning (kräver eget grävande i APIerna för Java Swing), lägg till fler komponenter till vår View, t ex:
- Utöka fönstret med en panel som skriver ut en lista av de polygoner som finns i visningspanelen. Det räcker med att skriva ut center point för dem. Informationen ska uppdateras i samband med animationen.
- Lägg till en knapp som startar och stoppar animationen.
- Lägg till en väljare där användaren kan ställa in vilken sorts polygon som ritas ut när nya polygoner klickas fram.
Övning 6-1: Immutability
Börja från koden som finns att ladda ner här.
- Vår gamla kod från förra veckan är uppdaterad enligt vad vi gjorde på föreläsningen idag: Vi har nu separata Model, View och Controller, som var och en gör det som förväntas av dem. Nästan.
-
Uppföljning från gamla ämnet: Animeringen fungerar inte. Vad är fel? Rätta felet.
- Hint: Det saknas en enda rad kod.
-
För resten av övningen, fokusera på package
TDA551.shapes
. Vi struntar för den här övningen i hur det sen ska användas – du har nu hatten av maintainer för detta paket, och vet ingenting omDrawPolygons
.-
Klassen
Shape
har ett väldefinierat gränssnitt. Denna klass är definitivt muterbar (än så länge). Men på grund av attPoint
också är muterbar, kan enShape
flyttas indirekt på annat sätt än som är tänkt via gränssnittet. Förstå hur detta ”trick” går till. Fundera över olika sätt vi skulle kunna lösa det. -
Ett (eller egentligen två) sätt är att definiera en egen, immutable,
Point
-klass. Detta kan antingen göras helt från scratch, eller genom att skapa en immutable wrapper runt den existerandePoint
-klassen. Testa båda dessa lösningar. -
Lös problemet även utan att definiera en egen
Point
-klass, och utan att förändra gränssnittet förShape
. Jo, det går! -
(lite svårare) Även
Shape
i helhet kan göras immutable, och ändå tillåta translation, skalning och rotation. Sättet vi gör det på är att vi, istället för att uppdatera det egna objektet, returnera en ny kopia med de nya värdena. Retur-typerna för de tre ”muterande” metoderna blir alltsåShape
, istället för void som nu.
-
Klassen
Övning 6-2: Fler Design Patterns
Börja från koden som finns att ladda ner här.
-
Vår gamla kod från förra veckan har fått ett helt nytt sub-paket
tda551.polygon
, som tillhandahåller ytterligare en variant på implementation av polygoner. Koden utanför paketet är (modulo några få tillsnyggningar) densamma som förra veckan. -
Sub-paketet
tda551.polygon
är inte helt färdigimplementerat ännu. Specifikt är metodernatranslate
,scale
ochrotate
konsekvent inte implementerade i de konkreta sub-klasserna.
-
Rita ett UML-diagram över det nya paketet
tda551.polygon
.-
För mer utmaning, inkludera även resten av filerna i paketet
tda551
. -
Ni behöver inte inkludera beroenden på filer som ligger utanför paketet, e.g.
JComponent
ellerPoint
.
-
För mer utmaning, inkludera även resten av filerna i paketet
- Identifiera de design patterns som används (inklusive i implementations-övningen nedan). Vissa har ni sett förut, några är nya.
-
Implementera metoderna
translate
,scale
ochrotate
i de klasser som ännu inte implementerar dessa.- Hint: Varje implementation kommer, korrekt gjord, bestå av högst två statements.
-
Specifikationen är som följer:
- En polygon representeras av en sekvens av nestade objekt, där varje objekt i kedjan representerar en specifik aspekt av polygonen.
-
En polygon initieras till en uppsättning punkter i en
BasePolygon
. -
Om den därefter e.g. roteras hanteras detta av
RotatePolygon
, etc. -
En polygon ska bara ha en rotation (etc) – roteras polygonen flera gånger ska dess
RotatePolygon
-objekt uppdateras.
-
För mer utmaning: Implementera en ny klass
PolygonGroup
som representerar en grupp av polygoner. Gör det möjligt att applicera e.g. rotation på hela gruppen. Vissa förändringar av existerande kod och struktur är nödvändiga.
Övning 7-1: Trådar
Börja från koden som finns att ladda ner här.
-
Vår gamla kod från förra veckan är uppdaterad enligt vad vi gjorde på förre föreläsningen.
- Gå igenom koden och inspektera implementationen av den Decorator och Chain of Responisbility designmönster.
-
Vi har lagt till en extra klass
Creator
som kör i en tråd och lägger till polygoner.-
I
DrawPolygons
finns kod som skapar och startar enCreator
tråd men tyvärr funkar det inte än. Fixa detta. Tips: kom ihåg varför den andra trafikljus iSignal
projektet började inte blinka. Tips 2: kör modellen i en tråd (användThread
ochRunnable
).
-
I
-
Skapa en klass
Terminator
som kör in sin egen tråd och tar bort en polygon. Meningen är att vi kommer att lägga till en polygon och sedan ta bort en, dvs i samma takt.-
I modellen finns nu också en metod (
removePolygon
) för att ta bort en polygon. - Kör programmet och kolla om det kör utan problem, dvs efter varje addition kommer en reduktion.
- Troligen kör programmet inte utan problem och addition/reduktion sker inte i samma takt. Dessutom kan det hända något ‘very bad’. Det kan vara så att vi försöker att ta bort ett element från en tom lista.
-
Lägg till en koll i
removePolygon
metoden (som första sats i metoden) som väntar tills listan är inte tomt och sedan fortsätter:while (polygons.isEmpty()) {}
-
Lägg till en koll i
addPolygon
metoden som väntar tills listan är tomt och sedan fortsätter:while (!polygons.isEmpty()) {}
- Kör programmet igen, det kan hända vi hamnar i en ‘Deadlock’ situation, dvs creatorn och terminatorn väntar på varandra.
-
Försök att åtgärda deadlocken med hjälp av metoderna
wait()
ochnotifyAll()
, kolla API på nätet.
-
I modellen finns nu också en metod (
- För mer utmaning: istället för begränsa att ha max en polygon i listan kan man definiera en viss kapacitet och se till att vi inte skapa fler än så. Man kan släppa kravet att det ska gå i takt och sedan skapa flera trådar som skapa och radera polygoner.