Laboration 1: Figurer i hierarki Bakgrund Två grundläggande tekniker i objektorienterad konstruktion är arv och komposition. Mål Laborationen har flera avsikter: 1. Ge kunskaper i hur program kan organiseras och struktureras med hjälp av arvshierarkier och komposition.. Ge grundläggande kunskaper i hur Javaprogram skrivs på filer, kompileras och hanteras. 3. Ge kunskaper om fundament i Javaprogram. Sista redovisningsvecka Vecka 40. Uppskattad tidsåtgång 14 timmar per gruppmedlem. Förberedelse/genomförande Börja med att lära er grunderna i Java genom att: Läsa/repetera Budd kapitel 4. Genomföra början av http://java.sun.com/docs/books/tutorial/index.html som innehåller en bra introduktion och enkel Java-kurs. Fortsätt sedan med att genomföra labben genom att: Läsa uppgiftslydelsen. Pröva sedan att konstruera, kompilera och testa någon egen enkel figurklass. Repetera seminarium 1. Repetera lämpliga delar av föreläsningsanteckningarna, speciellt föreläsning 7-8, samt Budd kapitel 8, 10, 11 och 1. Gör resten av labben. Tips "Kursen" på http://java.sun.com/docs/books/tutorial/index.html. Budd kapitel 11 innehåller en början till en hierarki av figurer, kapitel 1 diskuterar polymorfi och bland annat abstrakta metoder, kapitel 17 innehåller korta beskrivningar av några nyttiga grundläggande klasser och avsnitt 19.4 beskriver klassen Vector. Laboration 1: Figurer i hierarki 1 (6)
Obligatorisk uppgift Ni skall skriva ett program i vilket man kan skapa några olika typer av grafiska figurer. I beskrivningen av klasser, attribut och metoder som följer är inga modifierare (public, protected, private, final osv) angivna. Ni får själva välja dessa på lämpligt sätt. Vidare är ni fria att använda andra attribut eller egna metoder (till exempel hjälpmetoder) om ni kan motivera detta. Klasser Konstruera klasser för att hantera följande typer av grafiska figurer: rektangel kvadrat cirkel ellips polygontåg polygon. Ordna klasserna i en lämplig hierarki. Konstruera en abstrakt klass som alla andra klasser ärver ifrån. Attribut Varje objekt skall ha ett namn som anges vid skapandet av objektet. Ni måste också bestämma om ni behöver attribut för objektens position och utsträckning eller kanske för dess tjocklek, färg, mm. Vissa attribut behövs för vissa typer av objekt men inte för andra, till exempel en grupp av figurer (beskrivs nedan) behöver hantera medlemmar, en polygon en lista med dess punkter. Placera attribut som är gemensamma för alla klasser i den abstrakta klassen (till exempel attributet name). Instansmetoder Följande metoder skall implementeras. För varje metod är typ på eventuellt resultat och argument angivna. Namnet Figur är använt som namn på den abstrakta klassen. Instansmetoder för alla klasser, dvs metoder för meddelanden som skall ha mening för alla figurer: String name(), vilket namn har objektet (givet vid instansieringen)? String tostring(), som ger en textuell presentation av objektet, anropas bla av "print-metoder" (System.out.print(objekt);). Tex kan man i kvadratens version av metoden definiera att en den skall skrivas på följande sätt: "Kvadrat position: (10, 0) utsträckning: 100". Point location(), objektets position tagen i någon referenspunkt. Kan vara framräknad för till exempel polygoner (och senare grupper). void location(point), flytta objektet till viss position. Hitta också på något lämpligt förfarande för polygoner (och senare grupper). void moveby(point), flytta objektet en viss sträcka Rectangle bounds(), den minsta rektangel som omsluter hela objektet. boolean containspoint(point), befinner sig den givna punkten inom objektets omslutande rektangel eller ej? Laboration 1: Figurer i hierarki (6)
boolean intersects(figur), skär dom omslutande rektanglarna för mottagaren och argumentet varandra? float distanceto(figur), hur långt ifrån varandra befinner sig mottagaren av meddelandet och objektet som är givet som argument. För enkelhets skull kan avståndet mellan objektens "location()" användas. Instansmetoder för polygon och polygontåg: void addpoint(point), för att lägga till en ny punkt sist i figuren. Metoder som kan definieras i den abstrakta klassen skall definieras där. Metoder som måste implementeras i dom konkreta subklasserna skall deklareras som abstrakta i den abstrakta klassen. Konstruktörer Alla figurer skall skapas med åtminstone angivande av ett namn. Det skall inte att gå att skapa ett objekt utan att ange ett namn. Då en rektangel, kvadrat, ellips och cirkel skapas skall position och utsträckning anges. Inget annat sätt är tillåtet för dessa objekt. Utsträckningen för en rektangel och ellips ges som en punkt och för en kvadrat respektive cirkel som ett heltal. Till exempel new MinEllips("Ellips 1", //namn new Point(10, 10), //position new Point(50, 100)); //utsträckning new MinKvadrat("Kvadrat 1", //namn new Point(15, 15), //position 5); //utsträckning För polygontåg och polygoner skall det finnas konstruktörer där man kan skapa en av dem utan att de innehåller några punkter från början, med en punkt, två punkter, tre punkter eller en vektor av punkter. I alla fallen skall också (som vanligt) ett namn anges. Punkter skall sedan kunna läggas till efter instansieringen genom att metoden addpoint används. Grupp Konstruera en klass för hantering av grupper av dom andra objekten (dvs av instanser av subklasser till den abstrakta figurklassen). Gruppens klass skall också vara subklass till den abstrakta figurklassen (och därmed kunna ingå som element i en grupp). Alla dom attribut och metoder som skulle finnas för alla objekt skall också på lämpligt sätt implementeras för gruppen. En grupp skall kunna skapas på fyra olika sätt (dvs det skall finnas konstruktörer för dessa situationer): 1. Genom att ge den ett namn, utan att ange några medlemmar new MinGrupp("Grupp 1");. Med namn och två andra figurer new MinGrupp("Grupp ", rektangel, //rektangel och cirkel cirkel); //är variabler 3. Med namn och en lista av medlemmar där listan är en Vector new MinGrupp("Grupp 3", grupp.members()); 4. Med namn och skapad från en annan grupp new MinGrupp("Grupp 4", grupp3); Laboration 1: Figurer i hierarki 3 (6)
Skapa också följande instansmetoder: void addmember(figur), för att lägga till ett element sist i gruppen. Vector members(), en lista av alla gruppens medlemmar. Testprogram Skriv lämpligt testprogram som skapar objekt av olika slag, testar funktionaliteten samt övertygar laborationsassistenten om att det hela fungerar. Extrauppgifter X1. Effektivisering Gör följande två förbättringar: Förbättra containspoint(point)för ellips och cirkel så att du använder figurens rand istället för den omskrivna rektangeln. Du kan utnyttja ellipsens ekvation: ( x m a x ) ( y m + b y ) = 1 där (x, y) är en punkt på ellipsen, (m x, m y ) är ellipsens mittpunkt, a är halvaxelns längd i x-riktningen och b är halvaxelns längd i y-riktningen. För punkter inuti ellipsen är vänsterledet < 1. En annan effektivisering vore att utnyttja s.k. parameteröverlagring (eng. method overloading) där olika metoder med samma namn har olika antal parametrar eller olika typ på argumenten (se Budd avsnitt 1.3.4). Ett problem med detta angreppsätt är dock att Java binder överlagrade metoder statiskt med avseende på dom aktuella parametrarnas deklarerade typ. Så om en variabel x är deklarerad att vara en Figur men instansieras att vara en Rektangel och det finns två olika metoder m enligt följande, exempelvis i klassen Figur, m(figur p) och m(rektangel p) innebär följande anrop figur.m(x) att den förstnämnda av de två metoderna används (där figur är något objekt tillhörande Figur eller dess subklasser). Ovan beskrivna problem går att (hjälpligt) komma runt genom att "kasta om" typen på parametern vid anropet. Men detta är inte helt oproblematiskt, eller snyggt ur objektorienterad synvinkel, då man någonstans i koden behöver testa på aktuell variabels verkliga typ genom att använda den inbyggda operatorn instanceof på i stil följande sätt: if(x instanceof Cirkel) intersects((cirkel) x); else if(x instanceof Rektangel)intersects((Rektangel) x); osv Inte speciellt objektorienterat, eller hur!? En annan idé är att utnyttja s.k. double dispatching. Termen kan kort beskrivas på följande sätt: Dubbel uppslagning (eng. double dispatching) är en generell teknik för att effektivt välja en algoritm baserad på både mottagare och argument. Det hela går ut på att ett meddelande med argument resulterar i att ett annat (mer förklarande) meddelande skickas till argumentet med den tidigare mottagaren som argument. Laboration 1: Figurer i hierarki 4 (6)
Typexempel Antag att vi har tilldelat variablerna afraction och afloat värden på följande sätt: afraction = new MyFraction(10, 3); afloat = new MyFloat(1.0); och gör följande meddelandesändning: afraction.multiply(afloat) Båda MyFraction och MyFloat antas vara subklasser till klassen MyNumber. Resultatet av att meddelandet multiply skickas till afraction är att metoden i dess klass, dvs i MyFraction, utförs. Metoden i MyFraction skulle kunna se ut som följer: MyNumber multiply(mynumber anumber) { return anumber.productfromfraction(this); } Här skickas helt enkelt meddelandet productfromfraction, med det tidigare argumentet anumber, dvs afloat, som mottagare och med den tidigare mottagaren this, dvs afraction som argument. Detta meddelande är mycket mer specifikt och säger att argumentet ska vara en instans av MyFraction. Nu kan vi implementera olika varianter av productfromfraction i respektive subklass. Vilket gör att vi kan utnyttja att vi tex vet att argumentet tillhör MyFloat eller My- Fraction och effektivisera metoden (tex genom att returnera en ny MyFraction om både mottagare och argument är av denna typ). Ett annat alternativ är att helt enkelt kalla metoden för product och använda parameteröverlagring för att implementera olika versioner av metoden i subklasser som vill agera olika beroende av argumentets typ. För att detta skall fungera måste varje subklass implementera sin egen version av multiply som helt enkelt bara anropar product på samma sätt som productfromfraction anropades. I MyFraction skulle multiply se ut som följer: MyNumber multiply(mynumber anumber) { return anumber.product((myfraction) this); } Det går också (då kompilatorn antar att this som argument tillhör klassen koden skrivs i!) att utelämna "omkastningen" bara man skriver en multiply i respektive subklass, dvs man kan helt enkelt skriva om anropet av product på följande sätt: return anumber.product(this); Ett problem med dubbel uppslagning är dock att metoder för alla upptänkliga kombinationer av typer på mottagare och argument måste skrivas. Fast det räcker ofta med att skriva en generell version av respektive metod i den gemensamma superklassen (här MyNumber) och endast specialisera metoderna i dom subklasser där vi verkligen kan utnyttja "kunskaperna". Vi slipper i varje fall villkorssatser! Utnyttja double dispatching-teknik för att skriva mer exakta versioner av intersects(figur). Se åtminstone till att skärning mellan cirklar blir effektivare genom att utnyttja cirklarnas radier för att avgöra om dom skär varandra eller ej. Laboration 1: Figurer i hierarki 5 (6)
X. Dialog Lägg till möjligheter för användaren att i dialog med programmet skapa objekt och anropa lämpliga funktioner. Det skall åtminstone vara möjligt att: skapa namngivna instanser av alla olika figurtyper. gruppera namngivna objekt. avgruppera en grupp. fråga om två objekt skär varandra. flytta ett objekt. fråga om avståndet mellan två objekt. Se Budd kapitel 14 och speciellt StringTokenizer som beskrivs i avsnitt 17.8.3. Exemplet i avsnitt 19.7.1 kan också vara till hjälp. Delar av dialogen skulle kunna se ut i stil med följande: >Rect r1 10 10 0 30 >Circ c1 0 30 100 >Square s1 10 15 0 >Group g1 r1 c1 >dist g1 s1 5 Laboration 1: Figurer i hierarki 6 (6)