Rättnigsmall och kommentearer till
Tentamen i Algoritmer, Datastrukturer och
Objektorienterad Programmering, 10p
PSV2, HT 2001, 1 nov
Gränser: G19 p, VG 28p
Dessutom måste man ha minst 5p sammanlagt från uppgift 4 och uppgift 5.
Max är 40p. Bonus från inlämnade labbar kan ge ytterliggare max 4p
Uppgift 1 (2 + 2 + 2 + 2 + 2)
a)
Definiera den abstrakta datatypen (ADT) Stack.
Definiera metoderna addLast, getLast, removeLast + ev isEmpty och size = 1.5p
Definiera de Pre/Post-villkor som ska finnas med! = 0.5p (många missade det!)
b)
Visa, gärna med figurer, hur man kan implementera metoderna addLast, getFirst och
removeFirst för en Kö som är implementerad med hjälp av en länkad lista.
(Du får använda valfri typ av länkad lista - du väljer själv!)
Kommentar. Om man väljer en enkellänkad lista måste man lägga in i slutet (till höger) och
ta bort i början (till vänster). - 0.5p om man missade detta och använde enkellänkad.
c)
Visa med ord och figurer idén bakom algoritmen Qucksort.
Rita även en figur som visar ett exempel på hur det kan se ut när ungefär halva
sorteringstiden har gått. (Från början är alla talen osorterade och olika!)
De allra flesta har rätt på partitioneringen (1p)
En del missade att både vänster och höger sida ska sedan sorteras rekursivt med QS. (0.5p).
Många missade figuren: Efter c:a halva tiden är vänstra sidan helt klar-sorterad och högra
sidan osorterad (men alla där större än vänstra) (-0.5p)
d)
Bubblesort är O(n2) och Mergesort är O(n log n ).
Genom att studera själva algoritmerna kan man förstå att det blir så. Förklara varför!
Bubble-sort:Visa att den är en loop i loop, båda av Ordo(n) get Ordo(n2)
Mergesort: Visa att man man bryter ner problemet och löser delproblemen.
Varje omgång delproblem (alla Mergar) tar Ordo(n)
Nerbrytningen görs Ordo (log n ) gånger, totalt Ordo(n log n)
e)
Bubblesort sorterar 1000 tal på 0.3 sekunder. Mergesort sorterar samma tal på
0.005 sekunder. Uppskatta hur lång tid det tar att sortera 1000 000 tal i de båda fallen.
Det är 1000 gånger fler tal. Då tar det (ungefär)
Bubblesort: 0.3 *1000 * 1000 sekunder = 300 000 sekunder (3 hade rätt på denna)
(0.5p för 90 000 där man gör 0.3*0.3*1000*1000)
sid 1
3 hade rätt på denna!
Mergesort : 0.005 * 1000 * (log(10002) / log(1000) = 0,005 * 1000 * 2 = 10 sek.
(Även 0,005 * 1000 * log(1000) ≈ 50 godtogs)
1 hade helt rätt 1 hade varianten med 50, övriga hade fel på denna
sid 2
Uppgift 2 (2 + 2 + 2 + 2 + 1 + 1)
a)
Sätt in följande tal i en Hashtabell, dels i en öppen tabell, dels i en tabell med länkade
överskottslistor. Hashtabellen rymmer max 7 poster. Använd x % 7 som hashfunktion.
Talen som ska sättas in är: 9, 4, 21, 16, 50, 6, 27. (De ska sättas in i denna ordning, dvs 9
först, sedan 4 etc och sist talet 27)
x % 7 betyder "tag resten efter heltalsdivision med 7 ". Resten är alltså " det som blir över".
Exempel: 9 % 7 blir 2 (det blir 2 över när man delar med 7). 4 % 7 blir 4 (det blir4 över när
man delar med 7 även om själva divisionen ger 0 )
Detta hade Tomas tjatat om på flera lektioner (Javakodarens hemliga operator) och det
ingick även som en viktig del i lab 2 (för blinkande prylar mm).
Följande förslag till tabell (den öppna varianten) har lämnats in:
Tabell-förslag
21 50 9
4
21 9
4
9
16
4
50 9
21 50 9
21 50 9
21 50 9
21 50 9
21 50 9
50 21 9
50 21 9
4
21
9
21 50
6
50 27
4
50 9
4
9
16
16
16
21
21
4
4
4
4
16
6
16
6
9
21
21
4
50
6
16
16
16
16
16
27
4
4
6
4
21
6
50
27
6
27
6
6
6
6
6
4
27
27
27
4
16
6
6
27
50
27
27
27
27
27
6
16
16,6,27,9
50
16
16
27
27
antal svar h-funktion
14
x%7
6
x<7 ger 0, annars x % 7
3
2
2
1
(men en annan överskott. än föreg. )
1
(men en annan överskott. än föreg. )
1
(men en annan överskott. än föreg. )
1
1
1
tabell med 10 positioner
1
1
1
1
1
38 svarade på frågan. 14 hade rätt svar(den första raden i tabellen)
16 olika förslag har lämnats in (de 16 raderna i tabellen)
(De två översta ger 2p eller 2-, övriga varierande avdrag
beroende på hur "rimlig" h-funktionen är.
Man får alltid 1p om man gjort rätt på tekniken att lägga in i den öppna hashtabellen)
b)
Den abstrakta datatypen Map kan implementeras på många olika sätt.
Ge förslag (med motivering) på vilka datastrukturer som bör användas i följande tre
situationer:
-Insättning av nya element är mycket ovanligt. Sökning är mycket vanligt.
sid 3
Det är ganska få element (< 1000).
Här ska man absolut ha en datastruktur som det går fort att söka i!
Rätt svar är hash-tabell. Någotsånär bra är en sorterad struktur som man kan göra
binärsökning i eller binärt och balanserat träd.
Sökningen får inte vara sämre än Ordo(log n)!
De absolut sämsta alternativen är array, Vector och länkad lista, alla lika dåliga.
Av 28 som svarade på frågan ansåg 21 att array, Vector eller länkad lista vore bäst.
- Såväl insättning av nya som borttag av existerande element är mycket vanligt.
Man vet att antal element som vid något tillfälle är sparade aldrig överstiger 5.
Iteration över de sparade elementen är mycket vanligt.
Här passar en enkel struktur utan overhead bra.
Rätt svar är array, eller länkad lista. Vector är någotsånär, men en array som har 5 element
är bättre (Slipper lite overhead som finns i vektorn)
- Antal element är mycket stort, > 10 000 000 000
Rätt svar är B(n)-träd. Specialist på stora mängder!
I princip fick man 1p för ett rätt av tre, 0.5p för varje ytterliggare rätt.
(Egentligen borde man dock haft minus om man svarat vector eller array på första!)
c)
Följande algoritm kan användas när man ska besöka alla noder i en graf:
Definitioner:
G är grafen som har N stycken noder.
V är en mängd med redan besökta noder.
S är en mängd noder som står på tur att bli besökta
Algoritm:
1. Sätt V och S till tomma mängder.
2. Lägg in en startnod i S och samma nod i V.
3. Om S är tom, avsluta algoritmen (Klart!)
4. Tag bort första noden x i S och "besök" x (skriv ut x eller vad man vill göra).
5. För varje nod som kan nås direkt från x och som ej redan finns i V, lägg in den i V och
S.
6 Gå tillbaka till steg 3.
Föreslå lämpliga datastrukturer för V och för S för följande tre fall:
- man vill göra sökningen bredden först
- man vill göra sökningen djupet först
- man vill alltid ta "den närmaste" noden och bågarna representerar avstånd.
(Ge ett förslag för varje fall, det ska inte vara samma datastrukturer i alla tre fallen)
Rätt svar:
sid 4
S: en kö i fall 1, bredden först
S: en stack i fall 2, djupet först
S: en prioritetskö (t.ex en heap) i fall 3, närmaste bågarna
V: En hashtabell i alla tre fallen! V är bara till för att man snabbt ska söka efter en nod för
att se om den finns med i V eller ej.
Få hade överhuvudtaget svarat på vad V skulle vara, väldigt få hade svarat rätt.
0.5p för varje rätt i de tre S-fallen. 0.5 p för rätt V
d)
Vad menas med ett binärt sökträd. Förklara och ge exempel.
(Men du behöver inte berätta hur det skulle kunna implementeras!)
En del hade definierat ett binärt träd i största allmänhet vilket ger 1p.
Några hade definierat en heap i stället för ett sökträd.
e)
Hur många löv kan ett binärt träd med höjden 7 ha som mest. Svara med en exakt siffra!
(Om bara roten finns har den höjden 0)
128
f)
= 1p,
256 = 0.5 p
27
= 0.5p
Skriv pseudokod för en funktion som returnerar höjden på ett binärt träd.
höjden(T) :
Om T är tomt, returnera 0,
annars returnera 1 + max(höjden(T.vänstra) , höjden(T.högra))
(Ger höjden 1 för ett träd med bara en nod. Man kan dra bort 1 efteråt om man vill:
rätthöjd(T) = höjden(T) - 1
men detta är en pettitess)
Endast 1 hade helt rätt.
Man måste ha med rekursionen samt testen på vilken gren som blev djupast för att få
poäng.
sid 5
Uppgift 3 (2 + 2 + 1 + 2 + 2 + 1)
a)
Vad är skillnaden mellan statisk och dynamisk bindning av metoder?
Vid statisk bindning sker bindningen av metodnamnet till kod vid kompileringstillfället.
Vid dynamisk bindning sker det under runtime när själva anropet ska utföras.
(De allra flesta hade 2- på denna fråga, minus (-) för att man inte hade med "när själva
anropet ska utföras)
b)
När man ska konstruera klass-bibliotek som andra ska kunna använda är dynamisk
bindning av metoder ett väldigt värdefullt koncept och lösningen på ett generellt problem
som skulle kunna formuleras som:
Hur beskriver jag de algoritmer som mina klasser ska kunna hantera när jag vet vilka
metoder som ska användas men när dessa metoder inte finns definierade när själva
klassbiblioteket skrivs?
Berätta hur lösningen på detta problem kan se ut och berätta även vad detta har med
dynamisk bindning att göra.
Jag, som ska konstruera klassbiblioteket, vet alltså vilka metoder som ska utföras och i
min algoritm kan jag då bestämma när och om de ska utföras. Det är sedan upp till
användaren av mina klasser att definiera koden för metoderna. Eftersom anropen ligger i
klassbiblioteken och redan är kompilerade förutsätter detta dynamisk bindning för att
överhuvudtaget fungera eftersom koden inte finns tillgänglig vid kompileringstillfället.
Man kan definiera metoderna som dummies och användaren ska sedan överskriva dem
via arv.
Man kan även definiera dem som abstrakta och tvinga användaren att definiera dem.
Man kan även använda (eller definiera) ett interface och använda detta som typbeskrivning och på så sätt "få tillgång till" api för de metoder som interfacet beskriver.
Användaren ska sedan implementera interfacet och definiera metoderna, (Att däremot i
biblioteksklassen implementera ett interface löser inte detta problem - tvärtom!)
c)
Ge ett bra exempel på någon klass ur Javas klassbibliotek som bygger på det problem
och den lösning du just angivit.
Två väldigt tydliga exempel:
Frame som har metoderna paint, repaint och update där t.ex update anropar paint.
paint (och ibland update) ska skrivas över via arv när man vill rita i en frame.
Applet som bl.a har metoderna init, start och stop redan definierade men där det förutsätts
att man skriver över dessa via arv.
Ett annat exempel kan man se i inlämningsuppgiften i solitaireBuilder. Där finns
metoden setUpGames som ska anropas i början (t.ex från ett start-program)
sid 6
//---------------------------------------------------------- setUpGames()
public void setUpGames(){
createWindow();
// Create menys, buttons etc
createCards(); // Read card decks
createGames();
// Set up all solitaire games
}
createWindow, createCards och createGames ska anropas (i den tur-ordning som står
ovan) men patiensbyggaren ska ärva från solitaireBuilder och själv definiera
createWindow, createCards och createGames.
d)
Den Design Pattern som kallas Adapter kan i princip implementeras på två olika slags
sätt och du ska nu illustrera dessa två sätt i ett praktiskt exempel.
Det finns en klass Point vars beskrivning följer nedan och du ska nu skriva Javakod för
två olika "adapters" (Adapter1 och Adapter2) till klassen Point.
Class Point
Constructor
Point(double x, double y)
Methods
Point
double
double
void
void
getLocation()
getX()
getY()
setLocation(double x, double y)
setLocation(Point p)
Adapter1 ska användas för att man vill använda int i stället för double.
Adapter2 ska användas för att man man alltid vill ha x och y => 0.
Om x eller y är < 0 ska man byta tecken så att den koordinat som sparas
alltid är > 0. (x och y är fortfarande double i denna variant.)
Adapter1 och Adapter2 ska använda olika slags sätt för att göra en adapter.
Man ska dels använda köp dels använda arv för sina två adapters.
Eftersom man inte får ärva och definiera om en metod och ändra på dess typ (som man vill
göra i fall 1) kan man inte använda arv här utan vi använder köp-alternativet här.
Adapter 2 skriver vi med hjälp av arv.
Lösningarna blir:
class Adapter1 {
Point mypoint;
Adapter1(int x, int y){
mypoint= new Point((double) x,(double) y));
}
public Point getLocation(){return mypoint;}
public int
getX(){return (int)mypoint.getX();}
public int
getY(){return (int)mypoint.getY();}
public void setLocation(int x, int y){
mypoint.setLocation((double)x,(double)y);}
sid 7
public void
setLocation(Point p) {mypoint.setLocation(p);}
}
class Adapter2 extends Point{
Adapter2(double x, double y){super(abs(x), abs(y));}
public void setLocation(double x, double y){
super.setLocation(abs(x),abs(y));
}
private double abs(double x){
if(x < 0) return -x;
else return x;
}
}
(1p för varje deluppgift någotsånär OK)
e)
I kursboken nämns ett antal skäl för varför man skulle vilja använda arv. (Boken kallar
det för olika former av arv). Vilket är det vanligaste och viktigaste skälet?
(Ge även några signifikanta exempel!)
Vanligast: specialisation (1.5p)
(extension gav 1p)
Bra exempel: (0.5p)
f)
Vilket av skälen (arvs-formerna) är sämst i den meningen att man bör undvika arv i detta
fall? (Ge även något signifikant exempel!)
Limitation, dvs när man ska utesluta metoder eller inskränka existerande metoders
uppgifter.
Exempel: Om man ärver från Point ovan och gör en ny Adapter3 som förbjuder att x eller
y är < 0 så kan man förståss lägga in en test på detta, men det finns inget vettigt som man
kan göra i dessa fall eftersom Adapter3 även är en Point.
(Ej krav i talet men lite tydligare förklaring med ett kod-exempel följer här:
Point test = new Adapter3(1.0 ,2.0); // OK
test.setLocation(-2.0,1.0);
Vid rad två blir det "fel" i setLocation, men felet får inte kasta exeption(setLocation i Point
kastar inte exception). En ev. felutskrift någonstans är troligen programmet inte heller
beredd på (eftersom test är en Point).
sid 8
Uppgift 4 (1 + 0.5 + 0.5 + 1 + 1 + 1)
( alla delfrågor ska svaren vara Javakod)
a)
Skriv en Javaklass Card som kan användas för att representera spelkort.
Ingen egen konstruktor ska definieras till Card.
Följande metoder ska finnas och vara definierade i klassen:
getRank()
getSuite()
ska returnera kortets valör (mellan 1 och 13)
ska returnera kortets "färg"
0 = klöver, 1 = ruter, 2 = hjärter, 3 = spader
setRank(int x)
setSuite(int x)
ska sätta kortets valör (mellan 1 och 13)
ska sätta kortets "färg"
Svar:
class Card{
private int rank;
private int suite;
public int getRank() {return rank;}
public int getSuite(){return suite;}
public void setRank (int x){rank = x;}
public void setSuite(int x){suite = x;}
}
Anm:
setRank och setSuite kan (bör) även kontrollera att argumentet x
ligger i rätt intervall.
Detta skulle vara ett lätt tal och med minimala Java-kunskaper ska man
klara detta tal. Och om man inte klarar detta tal borde det även automatiskt rendera till
komplettering!
Avgörande är att införa de två instansvariablerna rank och suite som metoderna sedan
arbetar mot.
b)
Visa hur man kan använda klassen Card för att skapa tre "kort" spader 10, hjärter ess och
klöver kung.
Card kort1 = new Card();
Card kort2 = new Card();
Card kort3 = new Card();
kort1.setRank(10);
kort1.setSuite(3);
kort2.setRank(1);
kort2.setSuite(2);
kort3.setRank(13);
sid 9
kort3.setSuite(0);
c)
Lägg in en egen konstruktor i Card så att valör och färg kan anges direkt när man skapar
koden. Gör sedan om fråga b) fast med den nya konstruktorn istället.
Svar:
Lägg in konstruktorn
Card(int r, int s){rank = r; suite = s;}
eller
Card(int r, int s){setRank(r); setSuite(s);}
Plus ev. feltest (se anm tal a).
Alternativ två är att föredra då ev. feltester görs automatiskt (och då
man ev kan ändra setRank och setSuite via arv om man vill ha andra slags
kortvalörer)
uppgiften b) blir nu:
Card kort1 = new Card(10,3);
Card kort2 = new Card(1,2);
Card kort3 = new Card(13,0);
d)
Man vill nu kunna jämföra två kort med varandra för att se vilket som är "störst".
(Färgerna är i ordning klöver,ruter, hjärter, spader där spader är högst)
Lägg in en metod i Card som kan användas för detta. Metoden ska vara av typ boolean och
ha ett enda argument. Visa sedan hur metoden kan användas genom att jämföra spader 7
med hjärter dam.
I lösningen nedan antas att färgen (suite) är viktigast och först om
två kort har samma färg så kontrolleras deras valörer (rank)
lägg in följande metod i Card:
public boolean isGreater(Card c){
if (this.getSuite() == c.getSuite(){
if(this.getRank() >= c.getRank())
else return false;
// färgen lika, kolla valör
return true;
}else
if (this.getSuite() > c.getSuite()) return true;
else return false;
// min färg bättre
// min färg sämre
}
Anm1. Många hade svårt att förstå hur man kan kontrollera två kort med varandra om man
bara har ett argument i metoden. Men hela objektorienteringsidén bygger på att alla metoder
tillhör något objekt och alla metoder har tillgång till sitt objekts instansvariabler! Det ena
sid 10
kortet i det här fallet är alltså "jag" och det andra kortet är det man har i argumentet. (Jämför
t.ex med equals för strängar, s1.equals(s2), som använder precis samma idé!)
För att göra detta tydligare i svaret ovan har jag använt this överallt där det är relevant. Om
man utelämnar this (och alltså underförstår this) blir det i stället:
public boolean isGreater(Card c){
if (getSuite() == c.getSuite(){
if(getRank() >= c.getRank())
else return false;
// färgen lika, kolla valör
return true;
}else if (getSuite() > c.getSuite()) return true; // min färg bättre
else return false;
// min färg sämre
}
Anm2: Man kan använda rank och suite i stället för getRank och getSuite när man refererar
till det egna kortet, men antagligen inte när man refererar till c-kortet. I det senare fallet
måste då rank och suite deklareras public. Det är dock bättre att använda metoderna
eftersom det ger möjlighet till omdefiniering via arv.
Exempel på användning:
Card kort1 = new Card(7,3);
Card kort2 = new Card(12,2);
if(kort1.isGreater(kort2)) System.out.println("kort1 är större");
else System.out.println("kort2 är större");
e)
Ibland anses hjärter vara "bättre" än spader. Visa hur man kan via arv kan åstadkomma
detta.
Ärv och definiera om greaterp så att den tycker att hjärter är bättre än spader.
Metod1: Ren omdefiniering av isGreater
class NyCard extends Card{
...
public boolean isGreater(Card c){
if (getSuite() == c.getSuite(){// färgen lika, kolla valör
if(getRank() >= c.getRank()) return true;
else return false;
}else if(getSuite()
== 2) return true;
// hjärter bäst
else if(c.getSuite() == 2) return false;
//
-"else if (getSuite() > c.getSuite()) return true; // min färg bättre
else return false;// min färg sämre
}
sid 11
Metod 2: (Hacker-metoden. Sämre!)
Gör hjärter störst. Använd sedan pappas greaterp.
class NyCard extends Card{
...
public boolean isGreater(Card c){
if(getRank() == 2)
setRank(4);
if(c.getRank() == 2) c.setRank(4);
boolean answer = super.isGreater(c);
if(getRank() == 4)
setRank(2);
if(c.getRank() == 4) c.setRank(2);
return answer;
}
f)
Du vill nu kunna använda en existerande sorteringsmetod för dina kort. (Du vill t.ex kunnna
sortera 13 kort i en hand). I beskrivningen för metoden läser du bland annat att metoden
sorterar en Vector och att de element som ska sorteras (och som finns i vektorn) måste
implementera interface CanCompare. Du har inga problem med att lägga in korten i en
vektor men hur ska du ordna kravet på interfacet. Visa hur du via arv kan göra så att
korten får sorteras av de existerande metoden.
(Du kan även visa hur man gör utan att använda arv, men du får då inte full poäng för
talet)
Här följer interface CanCompare:
public interface CanCompare{
public boolean compareTo(CanCompare obj);
}
Svar:
class CompCard extends Card implements CanCompare{
public boolean compareTo(CanCompare obj){
return isGreater((Card)obj);
}
}
sid 12
Uppgift 5 (0.5 + 1 + 1 + 1 + 1.5)
Bland reglerna för hur man lägger en patiens kan man tänka sig följande:
När man klickar på ett kort ska programmet kontrollera om det är fritt.
I så fall kan användaren dra kortet till …
Kort räknas som fria om de antingen ligger med framsidan upp, oavsett var i högen de ligger.
Om kortet ligger med baksidan upp är det fritt bara om det är det översta kortet.
Man kan tänka sig att följande pseudokod implementerar denna regel:
- När ett mus-klick inträffar:
- Spar klick-positionen
- Gå igenom alla korthögar
- För varje hög, kontrollera denna hög
Gå igenom alla kort i högen
För varje kort:
täcker kortet click-positionen?
Om ja, är det fritt att ta?
Om ja, spar detta kort tills vidare
Om vi hittat något kort som är OK, ta det senast hittade och låt användaren "dra" det.
Nu finns en klass Player med en metod public void mousePressed(int x, int y) som man
kommer till vid musklick. Man kan nu tänka sig att implementera ovanstående direkt genom att
lägga i princip all kod i mousePressed.
Detta alternativ finns redovisat på tilläggsblad till Uppgift 4, alternativ 1.
Man kan även tänka sig ett mer objekt-orienterat alternativ med koden fördelat över flera
klasser. Detta alternativ redovisas på tilläggsblad till Uppgift 4, alternativ 2.
Här följer ett antal deluppgifter i anslutning till detta.
a)
Om vi struntar i effektivitet, finns det någon fördel med alternativ 1?
I alternativ 1 är hela algoritmen för hur man kontrollerar om ett kort är fritt samlat på ett
enda ställe. Det kan vara en fördel om man vill verifiera koden mot en given specifikation
(om specifikationen t.ex är given i liknande pseudokod). Det kan även vara en fördel om
algoritmen ska ändras och ändringen ska gälla oavsett vilken patiens det gäller och oavsett
vilken slags korthög det gäller. Det är lite svårt att hitta på ett bra realistiskt exempel i just
detta fall (eftersom det ska vara en ändring som inte har med enstaka patienser eller
korthögar att göra) men här är ett förslag i alla fall på en sådan generell förändring:
Vi vill att alla kort ska ha ett litet område runt omkring sig där man också ska kunna klicka
så om man missar kortet med några pixlar så ska dete inte göra något.
Det kan eventuellt vara mer läsbart i de fall man vill se hur hela algoritmen fungerar. Det
senare är lite fråga om tycke och smak.
sid 13
b)
Antag att olika korthögar har olika regler för om ett kort är fritt eller ej, berätta hur detta
skulle kunna implementeras i alternativ 1 resp alternativ 2. (Du behöver inte redovisa kod,
bara tala om principen)
I fall 1 är alla kort fria som ligger överst och är uppåtvända. Det bestäms av testerna
if(c.getFaceUp())
creturn = c;
else if(c == p.top()) creturn = c;
som ligger i den innersta while-loopen.
Istället måste man förse korthögarna med någon slags typ-kod som man ska kunna testa på.
Alternativt testar man på själva klassnamnet och använder det som en slags typ. Så fort
man hittar ett kort som man har klickat på (samma test som förut) ska man sedan istället
via if-satser (eller case-sats) göra anrop till rätt regler beroende på vilken typ av hög det
var.
I fall två finns samma regel som ovan redan implementerad i metoden isFree i klassen Pile.
Här ska man ärva från Pile och definiera om metoden isFree.
c)
Antag nu att all kod är kompilerad, så du kan inte ändra i någon källkod.
Hur skulle du nu göra för att lösa uppgiften som gavs i b).
Alternativ 1 är antagligen omöjlig. Visserligen kan man tänka sig att ärva från Player och
sedan skriva om hela metoden mousePressed (bökigt!) men var någonstans ska den nya
Player skapas? Eftersom det inte är meningen att man ska skapa en egen Player, finns det
troligen inte heller något ställe förberett där man kan skapa egna Player.
I alternativ två arver man från Pile och definierar om isFree.
d)
Rita ett anropsdiagram för alternativ 2 där man ser vilka klasser och vilka metoder som
är inblandade när en musklickning inträffar. (Metoder som har med iterationen att göra,
dvs iterator(), hasNext() och next(), behöver inte vara med).
Player
mousePressed -->
Solitaire
selectCard -->
Pile
selectCard -->
|
isFree -->
|
top
--> anrop till metod i en annan klass
| anrop till metod i den egna klassen
sid 14
Card
isHere
getFaceUp
e)
Nu är det ju inte alls säkert att man alltid ska låta användaren "dra" kortet när det är valt.
Vissa korthögar kanske vill göra något annat och vissa patienser kanske hellre försöker
flytta kortet direkt (eller markera kortet som "valt" eller något annat). Antag att följande
tre metoder finns tillgängliga i Player:
drag(Card c)
Användaren ska dra kortet
select(Card c)
Kortet markeras som utvalt
tryMove(Card c)
Programmet flyttar kortet direkt om det är möjligt
Visa hur man med enkla medel kan åstadkomma detta i alternativ 2. Du måste först ändra
lite i mousePressed (vad?) och i Pile (Lägg till en metod). Därefter ska du visa med
exempel två olika korthögar där den ena använder drag och den andra använder tryMove.
Detta ska åstadkommas via arv.
Varje kort måste veta vilken hög den finns i.
Vi förutsätter att korten har en metod getPile som fixar detta, annars måste getPile
definieras för korten.
Varje hög måste kunna komma åt nuvarande spelare (Player).
Man kan tänka sig att det finns en metod getPlayer i Pile som fixar detta, men i svaret
nedan, (mer för tydlighetens skull), låter vi spelaren själv följa med som argument i anropet
till den nya metoden activateCard. Anropet ligger i Player och definitionen finns i Pile.
.
I klassen Player:
i mousePressed:
Byt ut if- satsen:
if (c != null) drag(c);
mot:
if (c != null) c.getPile().activateCard(this, c);
Lägg in de tre nya metoderna:
drag(Card c)
select(Card c)
ryMove(Card c)
// Användaren ska dra kortet
// Kortet markeras som utvalt
// Programmet flyttar kortet direkt om det går
(Dessa skulle enligt uppgiften redan finnas i Player)
I klassen Pile definieras sedan den nya metoden activateCard:
class Pile{
...
public void activateCard(Player p, Card c){
p.select(c); // Att göra select är default
}
...
sid 15
class DragPile extends Pile{
...
public void activateCard(Player p, Card c){
p.drag(c); // Den här högen gör drag
}
...
class TryPile extends Pile{
...
public void activateCard(Player p, Card c){
p.tryMove(c); // Den här högen gör tryMove
}
...
sid 16