Laboration 3: Refaktorering av spelramverket Pelle Evensen (2011) & Christer Carlsson (2015)

Relevanta dokument
TDA550 Objektorienterad programvaruutveckling IT, forts. kurs Laboration 3 Refaktorering av spelramverket

DAT043 - Föreläsning 7

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 11 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Arv. Fundamental objekt-orienterad teknik. arv i Java modifieraren protected Lägga till och modifiera metoder med hjälp av arv Klass hierarkier

Två designmönster, MVC och Observer/Observable. Objektorienterad programvaruutveckling GU (DIT011)

Model View Controller. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

TUTORIAL: SAMLING & KONSOLL

Observer Pattern och MVC. Objekt-orienterad programmering och design (DIT953) Niklas Broberg, 2018

TDDC30 Programmering i Java, Datastrukturer och Algoritmer Lektion 3

Programutvecklingsprojekt Projektgrupp Elvin. Detailed Design Document

Design och konstruktion av grafiska gränssnitt

Objektorientering: Lagring och livstid

Observer Pattern och MVC. Objekt-orienterad programmering och design Alex Gerdes, 2016

TDDD78 Objektorientering: Lagring och livstid

Föreläsning 2. Objektorienterad analys och design. Analys: att modellera världen. Design: att strukturera program.

Lab5 för prgmedcl04 Grafik

Övningen vill visa på vikten av valet av datastruktur, trots att de ofta erbjuder samma funktionalitet genom sina gränssnitt.

LÖSNINGSFÖRSLAG Programmeringsteknik För Ing. - Java, 5p

Objektorienterad Programkonstruktion. Föreläsning 6 23 nov 2015

Föreläsning 8 - del 2: Objektorienterad programmering - avancerat

ITK:P1 Föreläsning 4. Grafiska gränssnitt i Java. AWT-komponenter

Separation of Concern. Objekt-orienterad programmering och design (DIT953) Niklas Broberg / Johannes Åman Pohjola, 2018

Objekt-orienterad Programmering och Design. TDA551 Alex Gerdes, HT-2016

Tentamen i EDAF25. 1 juni Skrivtid: Skriv inte med färgpenna enda tillåtna färg är svart/blyerts.

Tentamen. 2D4135 vt 2004 Objektorienterad programmering, design och analys med Java Torsdagen den 3 juni 2004 kl

F8 - Arv. ID1004 Objektorienterad programmering Fredrik Kilander

Principles of subclasses. Objekt-orienterad programmering och design Alex Gerdes, 2018

Kopiering av objekt i Java

Innehåll. dynamisk bindning. och programmering CRC) u Arv, polymorfi och

Spelet i sig är inte avancerat men projektet ställer en del krav på implementationen bland annat:

Laboration 2: Designmönster

Laboration 2: Designmönster

Design och konstruktion av grafiska gränssnitt

725G61 - Laboration 7 Implementation av ett API. Johan Falkenjack

Tor Sterner-Johansson Thomas Johansson Daniel Henriksson

Properties. Användbara metoder som kan anropas i propertychanged:

PROGRAMMERINGSTEKNIK TIN212

TDDD78, TDDE30, 729A Typhierarkier del 3 När och hur vill vi använda dem? Några Best Practices

Objekt, klasser. Tillstånd Signatur Kommunikation Typ. Fält, parametrar och lokala variabler. Konstruktorer Metoder DAVA15

TDDC30 Programmering i Java, Datastrukturer och Algoritmer Lektion 3

Objektorienterad programmering. Grundläggande begrepp

Fyra i rad Javaprojekt inom TDDC32

Objektorientering: Lagring, räckvidd och livstid

Objektorienterad programmering med Java Swing: Händelser, lyssnare och applets

Objektorienterad Programkonstruktion. Föreläsning jan 2016

Läs detta! Uppgifterna är inte avsiktligt ordnade efter svårighetsgrad. Skriv ditt idnummer på varje blad (så att vi inte slarvar bort dem).

Föreläsnings 11 - GUI, Händelsestyrda program, MVC

Snake App Rapport - Snake App Rapport Utskriven/PDF Export: Copyright Version 1.2 Sidan 1 av 9.

Arv: Fordonsexempel. Arv. Arv: fordonsexempel (forts) Arv: Ett exempel. En klassdefinition class A extends B {... }

Objektorienterad Programkonstruktion. Föreläsning 3 7 nov 2016

EDAA01 Programmeringsteknik - fördjupningskurs

KARLSTADS UNIVERSITET 12/8/09 informatik & datavetenskap Johan Öfverberg, Kerstin Andersson Laboration 4, ISG A04 och DVG A08 HT-09

Redogörelse för utvecklingsprocessen av spelet The Legend of Chalmers

2I1049 Föreläsning 5. Objektorientering. Objektorientering. Klasserna ordnas i en hierarki som motsvarar deras inbördes ordning

Föreläsningsmaterial (Arv) Skrivet av Andreas Lund

TDDC30 Programmering i Java, Datastrukturer och Algoritmer Lektion 5. Laboration 4 Lådplanering Exempel på grafik, ett avancerat program Frågor

DVGB05 Grafiska användargränssnitt. Mjukvarudesign med Model-View-Controller

Föreläsning 5. När skall man använda implementationsarv? När skall man använda implementationsarv?

Verktyg och Utvecklingsmiljö. Jochim von Hacht

DAT043 - föreläsning 8

Objektorienterade programmeringsspråk. Objektorienterade språk. Den objekt-orienterade modellen. Jämför med icke-oo

Objektorienterad programmering

Föreläsning 4. Klass. Klassdeklaration. Klasser Och Objekt

CliMate följer Tre-lager-arkitektur. Domänobjekt - domänlogiklagret. Viktiga domänklasser i CliMate. De tre lagren. Paketen i CliMate:

Static vs Dynamic binding Polymorfism. Objekt-orienterad programmering och design (DIT953) Niklas Broberg, 2018

1.1 Runnable och Thread

Kort om klasser och objekt En introduktion till GUI-programmering i Java

Observer Pattern och MVC. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2017

Laboration 3 GUI-programmering

"Är en"-relation. "Har en"-relation. Arv. Seminarium 2 Relevanta uppgifter. I exemplet Boll från förra föreläsningen gällde

TDDD78 projekt: Tower Defence

Separation of Concern. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016 Johannes Åman Pohjola, 2017

Objektorienterad programmering i Java I

Det här dokumentet är till för att ge en översikt över ASP.NET MVC samt hur WCF Services används från.net applikationer.

Tentamen ID1004 Objektorienterad programmering October 29, 2013

Objektorienterad Programkonstruktion, DD1346. Tentamen , kl

Introduktion. Objekt-orienterad Programmering och Design (TDA551) Alex Gerdes, HT-2016

Föreläsning 13 Innehåll

Lösningar till tentamen i EDAF25

Static vs Dynamic binding Polymorfism. Objekt-orienterad programmering och design Alex Gerdes, 2016

Idag. statiska metoder och variabler. private/public/protected. final, abstrakta klasser, gränssnitt, delegering. wrapper classes

Objektorienterad Programkonstruktion. Föreläsning 2 2 nov 2016

Inkapsling (encapsulation)

Det ska endast finnas två bilder av samma typ på spelplanen.

Objektorienterad Programkonstruktion. Föreläsning 3 9 nov 2015

Ett objekt... Exempel: Om ni tittar er runt i föreläsningssalen ser in många olika fysiska föremål:

Översikt Föreläsning 1. Trivicalc. Vad är trivicalc? En cell. Områden på skärmen. SMD168/SMD135 Fredrik Bengtsson

Abstrakta datatyper Laboration 2 GruDat, DD1344

Klasser och objekt. Henrik Johansson. August 20, 2008

Tentamen. Datalogi I, grundkurs med Java 10p, 2D4112, Lördagen den 30 november 2002 kl , salar E33, E34

Teoretisk del. Facit Tentamen TDDC (6)

Agenda. Objektorienterad programmering Föreläsning 13

Föreläsning 1, vecka 6: Abstraktion genom objektorientering

Föreläsning 17 UTBLICK: FORTSÄTTNINGSKURSER I DATAVETENSKAP + ANDROID

Mål med lektionen! Repetera och befästa kunskaperna.

Introduktionsmöte Innehåll

Principles of subclasses Objekt-orienterad programmering och design (DIT953) Niklas Broberg, 2018

Mjukvarudesign. Designprocessen. Teknisk design. Konceptuell design

Lösningar till Fiktiv Tentamen på kursen. 2D4135 Objektorienterad programmering, design och analys med Java vt2004. Teoridel

Övning 5. TDA550 - Objektorienterad programvaruutveckling, fk

Transkript:

Laboration 3: Refaktorering av spelramverket Pelle Evensen (2011) & Christer Carlsson (2015) TDA550 Sammanfattning I denna laboration ska vi identifiera, och i flera fall åtgärda, tillkortakommanden i det spelramverk som ni använde i laboration 1. Allmänt I strikt bemärkelse ska vi inte bara ägna oss åt refaktorering 1, då vi även kommer att ta bort vissa beteenden, metoder eller variabler som finns i det gamla ramverket. Det gamla ramverket ska kunna simuleras med hjälp av det nya, givet att man skapar lämpliga hjälpklasser. Vi ska också använda några kända designmönster. Vi ska punkt för punkt diskutera specifika problem med det gamla ramverket och stegvis försöka åtgärda några av dem. Klasserna som ingår och berörs är: CompositeTile GameOverException Main Constants GameTile Position CrossTile GameView RectangularTile GameController GoldModel ReversiModel GameFactory GUIView RoundTile GameModel IGameFactory SquareTile Implementation av ett nytt spel För att få något konkret och ganska annorlunda jämfört med Snake-spelet ska vi använda Othello/Reversi 2 som första modell. Detta kommer mer konkret att påvisa några av begränsningarna i det befintliga ramverket. Spellogiken för Reversi ligger i paketet orig2011.v0 (klassen ReversiModel). Tips om tillvägagångssätt Skapa ett nytt paket för varje deluppgift För varje deluppgift skall ni skapa ett nytt paket. Om något går fel har ni då hela tiden en bra version att falla tillbaks på. När ni ändrar något gör ni detta bara i det nya paketet. Programmet ska gå att köra Efter att ni genomfört varje deluppgift skall programmet ha (minst) den funktionalitet det hade innan ni löste uppgiften. Förvissa er om att programmet går att köra och beter sig som det skall innan ni påbörjar nästa deluppgift. Att lägga till nya spel För att lägga till spelet Reversi räcker det att lägga till en ny Main-klass och en ny implementation av en lämplig GameFactory (som implementerar IGameFactory). 1 Wikipedia ger följande denition av "refactoring": "Refactoring is the process of changing a computer program's source code without modifying its external functional behavior in order to improve some of the nonfunctional attributes of the software. Advantages include improved code readability and reduced complexity to improve the maintainability of the source code, as well as a more expressive internal architecture or object model to improve extensibility." 2 http://en.wikipedia.org/wiki/reversi Laboration 3 1

Deluppgift 1 Det paket ni får att ugå ifrån heter orig2011.v0. Skapa ett nytt paket, orig2011.v1. Markera klasserna Main och GameFactory i paketet orig2011.v0 i Eclipse, välj "Copy". Högerklicka på det nya paketet orig2011.v1 i er src-katalog i projektet och välj "Paste". Nu skall ni ha fått en kopia av just dessa klasser till det nya paketet. Kopiera inte direkt via filsystemet, då kommer ni att behöva byta paketnamn på alla klasser manuellt. Byt namn på GameFactory till ReversiFactory (i ert nya paket, orig2011.v1). Modifiera sedan ReversiFactory så att det går att spela både Gold och Reversi. Vill ni lägga till Snake från laboration 1 får ni givetvis göra detta, men då måste ni också genomföra de förändringar som så småningom kommer att krävas även i Snake. Ett enklare alternativ är att lägga till Snake när ni är klara med hela labben. Detta är dock frivilligt. Berörda klasser/interface: Main samt ReversiFactory (ny klass). Abstrakta klasser kontra interface Fördelen med abstrakta klasser i stället för interface är som bekant att man kan tillhandahålla en viss grundimplementation och sedan bara implementera de delar som varierar. En av nackdelarana (i alla fall i Java) är att man bara kan ärva implementation från en klass (som i sin tur kan ärva från en annan klass, o.s.v.). Detta medför att vi tvingas återanvända kod, även om den inte passar särskilt bra för uppgiften. I lab 2, om geometriska former, såg vi hur man kan komma ifrån denna begränsning, interfacet GeometricalForm implementerades av en abstrakt klass vilka de konkreta klasserna Line, Circle, etc. ärvde från. Detta gör det möjligt att välja hur vår implementation ska se ut; endast om vi har nytta av implementationen som tillhandahålles av den abstrakta klassen använder vi klassen. Studera klassen ReversiModel. Denna klass tvingas att använda det befintliga spelbrädet på ett mycket konstlat sätt. Mer specifikt är inte ett fält av fält av GameTile något bra sätt att hålla reda på tillståndet på brädet när vi skall utföra beräkningar eller förändringar. Det fungerar däremot bra för presentation av spelet. Betydligt bättre är att istället bara använda instansvariabeln ReversiModel.board. Vad ett anrop till getgameboardstate resulterar i skall kunna härledas från instansvariablerna i ReversiModel som representerar: brädet (board) markörpositionen (cursorpos) vems tur det är (turn) Ingen annan lagring av brädet får ske än den som redan finns i dessa tre variabler. Detta inkluderar eventuella variabler i superklassen (GameModel). Med nuvarande utseende på ReversiModel uppdateras både board och det ärvda spelbrädet (från GameModel). Detta är en dålig design; all logik som rör brädet måste dubbleras. När vi duplicerar data blir det svårare att hålla dem i fas. I första hand bör man försöka härleda/beräkna värden från befintlig information snarare än att lagra dem. Se gärna riktlinje på sid. 91 i [Skr09]. Undantag från denna princip kan göras om klassen är icke-muterbar och beräkningarna utgör ett mätbart prestandaproblem. Laboration 3 2

Deluppgift 2 Bygg om ramverket så att GameModel blir ett interface. Den (mycket lilla) funktionalitet den befintliga klassen GameModel tillhandahåller bör man lägga över i en hjälpklass, lämpligt namn kan vara GameUtils. Klassen GameUtils får inte ha något tillstånd (instans-/klassvariabler). Metoderna ska direkt manipulera objekt av typen GameTile[][]. Om GameModel ska vara ett interface, ska då någon av de metoder som har synlighet protected finnas med? Interfacet bör endast ha 3 (eller möjligen 4) metoder deklarerade. Berörda klasser/interface: GameModel, GoldModel, IgameFactory, Main, ReversiFactory, ReversiModel samt GameUtils (ny klass). Deluppgift 3 Klassen GameTile lider av samma problem som GameModel gör. Bygg om klassen GameTile så att den istället blir ett interface. Här är det befogat att skapa en ny klass som har samma beteende som konkreta instanser av GameTile har. Kalla denna nya klass BlankTile (den ska givetvis också vara en GameTile). Berörda klasser/interface: BlankTile (ny klass), CompositeTile, CrossTile, GameTile, GoldModel, RectangularTile, RoundTile och SquareTile. När denna deluppgift är klar bör/skall ni diskutera vad ni gjort och hur ni gjort det med en handledare. Olämpliga kopplingar och brist på flexibilitet Då vi prövar att implementera något annat än Snake med ramverket blir några begränsningar ganska tydliga: 1. Det är svårt eller omöjligt att på ett elegant sätt lägga till fler komponenter som kan visa status. I Reversifallet hade det varit trevligt att kunna se vems tur det är och vad poängställningen är. 2. Det enda sättet att få en spelplan med olika utseende på underlag och spelpjäser är genom att göra mer komplicerade spelbrickor. Detta finns som en nödlösning i CompositeTile. 3. Det är långt i från alla spel som har nytta av en timer som skickar uppdateringsanrop. 4. Timern går på fasta tidsintervall. Vi ska i tur och ordning se vilka förändringar och strukturförbättringar vi kan genomföra för att lösa problemen ovan. Problem: Hårda kopplingar mellan modell-, vy- och kontrollklasser Flera av kopplingarna mellan modell, vy och kontroller sätts upp då de olika objekten instantieras. Här finns ett utmärkt tillfälle att bygga om så att vi får lösare kopplingar. Det arkitekturella mönstret Model-View-Controller (MVC) kan tillämpas. Dessa tre abstrakta entiteter (varje del av M, V och C kan bestå av fler än en klass) har en ansvarsfördelning enligt följande: Laboration 3 3

Model - Svarar på frågor om tillstånd (oftast genom anrop från vyn). - Uppdaterar systemets datatillstånd (oftast genom anrop från kontrollern). - I händelsedrivna system (typiskt applikationer med grafiska gränssnitt) gör modellen sina observatörer (en eller flera vyer) uppmärksamma på att de ska reagera. View - Presenterar information från modellen (eller modellerna) på ett lämpligt sätt. Är oftast en grafisk komponent i fönsterbaserade system. - Flera (olika) vyer kan finnas för en och samma modell. Controller - Omvandlar användarinteraktion till lämpliga anrop till modellen. MVC implementeras vanligen genom någon av dessa varianter: Passiv MVC Aktiv MVC Modellen ansvarar för att rätt information skickas till vyn. Detta kan också kallas för strikt "push", eftersom modellen "knuffar" ut data till vyn. Här är alltså vyn mer passiv, den agerar på de data den får men frågar inte nödvändigtvis efter mer. Anropskedjan är då typiskt C M V. Kontrollern ansvarar för att vyn får information om att något uppdaterats, vyn frågar därefter modellen efter den information som behövs. Vyn intar här en mer aktiv roll - den avgör själv vilka data som behövs för att presentationen ska förändras. Anropskedjan blir typiskt C M, C V M. Aktiv MVC är i grova drag den struktur ramverket har från början: - GameController känner till (och anropar) GameModel och GameView. - GameView känner till och anropar GameModel. - GameModel känner inte till GameController och GameView, men kan svara på anrop från dem genom sina publika metoder. Flera av defekterna i listan på sida 3 ska vi börja komma till rätta med genom att mer strikt tillämpa passiv MVC. En central idé i MVC-mönstret är att modellen ska ha lösa kopplingar till sina vyer. Detta verkar gå dåligt ihop med passiv MVC, men här är tricket att modellen bara känner till lyssnare eller observatörer. Dessa är en abstraktion, modellen har inte dessa förutbestämda utan det är upp till varje observatör att tala om för modellen att de vill lyssna efter förändringar. Vi skall börja med att göra GameModel observerbar. Vi kan dock inte nyttja java.util.observable för detta. Skälet är att Javas standardklasser för designmönstret Observer inte är så genomtänkta som man skulle önska. java.util.observable är en klass och inte ett interface. En konsekvens av detta blir att man inte kan vara observerbar med mindre än att man ärver implementationen! Och GameModel är ett interface! Laboration 3 4

Deluppgift 4 Skapa ett nytt interface, IObservable. Detta bör endast ha två metoddeklarationer: addobserver(propertychangelistener observer) removeobserver(propertychangelistener observer) Gör GameModel observerbar genom att utöka interfacet IObservable. Alla klasser som är spelmodeller måste implementera metoderna i interfacet IObservable. Detta görs lättast genom att respektive modellklass delegera till en instans av PropertyChangeSupport. Tänk på att lämpliga metoder i erat PropertyChangeSupport-objekt måste anropas när vi vill göra observatörerna uppmärksamma på att något inträffat. Berörda klasser/interface: GameModel, GoldModel, ReversiModel och IObservable (nytt interface). Deluppgift 5 När GameModel blivit observerbar bör vi pröva och se att uppdateringar till vyer inträffar när de ska. Ordna så att GameView implementerar PropertyChangeListener. Varje GameView-objekt ska lyssna på sin egen respektive modell. När något händer (från modellen) räcker det med att anropa repaint i GameView. Berörda klasser/interface: GameController, GameModel, GUIView, GoldModel och ReversiModel. Nu återstår att låta kontrollern släppa taget om vyn så att alla uppdateringar av vyerna endast sker genom att modellen anropar sina lyssnare. Många av klasserna i Swing-paketet har något dubbla roller. Dels kan de presentera information grafiskt, men de kan också lyssna efter användarhändelser. GameView ärver JComponent. Det är denna JComponent som skall lyssnas på för att kontrollern skall kunna skicka händelserna vidare. Det enda GameController skall göra med vyn är att lägga till sin tangentbordslyssnare. I övrigt skall den lämna vyn i oförändrad. Deluppgift 6 Låt uppdateringarna till vyn gå genom modellen, utan att kontrollern direkt anropar vyn. Nu har vi ett utmärkt tillfälle att också göra olika uppdateringsintervall möjliga. Om vi lägger till metoden getupdatespeed() till modellen kan den själv avgöra hur ofta eventuella tidsstyrda händelser skall ske från kontrollern. Givet att vi betraktar hastigheten som en modellegenskap (rimligt då den kan hänga ihop med spellogiken) är det en klar fördel att kontrollern inte bestämmer över detta. Här bör vi också i kontrollern möjliggöra direkta tangentbordstryckningar. Detta införs lättast genom att enqueuekeypress anpassas så att den skickar tryckningarna direkt till modellen om getupdatespeed() antar särskilda värden (t.ex. getupdatespeed() < 0). Glöm inte att pröva att det fortfarande går att byta eller starta om spel, även om man har en speltyp utan uppdateringsintervall. Berörda klasser/interface: GameView. Laboration 3 5

Fler och mer specifika vyer Som vi sett tidigare har vi inte kunnat välja hur vi vill presentera eventuella poäng från spelen; den enda möjligheten har varit att låta modellen direkt sköta det. När vi har möjlighet att observera modellen blir detta mindre komplicerat. Deluppgift 7 Lägg till en lyssnare av typen ReversiScoreView i ReversiFactory när en Reversi-modell skapas. För en specifik lyssnare är det tillåtet att gå på klasspecifika egenskaper. Alltså kan ReversiScoreView anropa getblackscore() och getwhitescore(). Det är efter lämpligt test Nu har vi löst problem 1, 3 och 4 från vår lista! Bortsett från att Reversi-spelet nu inte köar massa tangentbordstryckningar ser det ungefär likadant ut. Om vi däremot skulle välja att implementera ett nytt spel, t.ex. Tetris, kommer vi troligen att upptäcka att det blir betydligt enklare. För just Tetris behöver vi minst två funktioner som det ursprungliga ramverket inte hade: möjlighet att ändra fördröjningen mellan händelser samt möjlighet att visa nästa kloss och/eller poängställning på ett snyggt sätt. Frivillig uppgift (evt.getsource().getclass() == ReversiModel.class) tillåtet att göra antagandet att den som genererat uppdateringen är av typen ReversiModel. ReversiScoreView kan ha en mycket enkel propertychange-metod, låt den bara skriva ut svarts och vits poäng samt vems tur det är att spela. Berörda klasser/interface: ReversiFactory och ReversiScoreView (ny klass). Det finns givetvis både fler funktionsmässiga och strukturella defekter, både i ramverket och våra modeller. I mån av tid, identifiera så många problem som möjligt (förutom dem vi redan nämnt) och diskutera med en handledare. Åtgärda gärna så många brister som möjligt men försäkra er om att ni har en version av koden som bara innehåller lösning fram till uppgift 7. Berörda klasser/interface:??. Referenser [Skr09] Dale Skrien. Object-Oriented Design using Java. McGraw-Hill, international edition, 2009. Laboration 3 1