...
I versjon 1 inneholdt sekvensen tall mellom 1 og 9, men det er ikke noe i veien for å tillatte flere enn disse ni verdiene. Verdiene trenger egentlig ikke være tall heller, det kan være hva som helst bare en kan sjekke dem mot det brukeren gjentar. I en grafisk versjon kan det f.eks. være aktuelt å klikke på figurer med ulike fasonger, farger og/eller motiver. Logikken i Memory-objektet blir essensielt den samme. Forskjellen er hovedsaklig at når en skal utvide sekvensen, så genereres et tilfeldig tall (som før) som brukes til å plukke et element fra en liste med alle mulige verdier. Denne lista er det naturlig å oppgi når Memory-objekter opprettes. Klassediagrammet for Memory under, viser hva som er endret:
Hva skal ? erstattes med? |
Vi ser at Memory-objekter klassen nå vil inneholde inneholder en liste over alle mulige verdier kalt possibleItems, som elementene i expectedItems er plukket skal plukkes fra. Vi har også lagt til en konstruktør , som krever at en oppgir samlingen av mulig verdier, når Memory-objekter opprettes.
Et viktig spørsmål er valg av typer for attributtene. Den generelle regelen er å bruke så generelle typer som mulig og velge grensesnitt-klasser fremfor implementasjoner, for å gi størst frihet ved kodingen. Derfor har vi valgt å bruke List fremfor Collection for både possibleItems og expectedItems. List utvider Collection med metoder for å hente ut elementer basert på posisjon, og dette er nyttig når vi skal velge ut tilfeldige elementer fra possibleItems i nextItem() og sammenligne et nytt element fra brukeren med siste element i expectedItems i acceptItem(...). Argumentet til Memory-konstruktøren er imidlertid av typen Collection, fordi vi kun trenger å gå sekvensielt gjennom det, når vi kopierer elementene inn i den interne lista.
Både List og Collection er generiske typer, dvs. typer som kan spesialiseres til (å bare kunne inneholde) bestemte element-typer, og spørsmålet er nå hva skal disse spesialiseres til? Det mest generelle er å velge Object, slik det er vist under, fordi vi da kan støtte hukommelsesspill med alle typer objekter. Ulempen er at vi da ikke får noen typesjekk på verdiene vi gir som argumenter til konstruktøren og acceptItem. Vi får på en måte for mye frihet: Ikke var kan vi spille med alle mulige objekter, men vi kan blande ulike typer i samme spill og dermed skape rot for oss selv. Det vil f.eks. være lov å putte String-objekter inn i possibleItems og kalle acceptItem(...) med Integer-objekter, selv om det ikke gir mening:
|
|
Et alternativ er å gjøre Memory generisk ved å introdusere et typeparameter T og bruke typeparameteret i attributt-deklarasjonene, som vist under. Dette vil la oss spille med alle typer objekter, men hvert enkelt hukommelsesspill kan bare spilles med en bestemt type, som bestemmes (statisk) når Memory-objektet opprettes. Hvis vi da prøver å blande ulike typer, så vil det gi typefeil (i editoren og ved kjøring):
|
|
Her er fullstendig kode for klasser:
Code Block | ||
---|---|---|
| ||
public class Memory<T> {
private List<T> possibleItems;
private List<T> expectedItems;
private int acceptedCount;
public Memory(Collection<T> possibleItems) {
this.possibleItems = new ArrayList<>(possibleItems);
expectedItems = new ArrayList<T>();
acceptedCount = 0;
}
public Memory(T... possibleItems) {
this(Arrays.asList(possibleItems));
}
public T nextItem() {
int index = (int) (Math.random() * possibleItems.size()); // generate random index
T nextItem = possibleItems.get(index); // look up value
expectedItems.add(nextItem); // add to sequence
acceptedCount = 0; // reset accepted counter
return nextItem; // return new item
}
public Collection<T> nextItems(int count) {
Collection<T> items = new ArrayList<T>();
while (count > 0) {
items.add(nextItem());
}
return items;
}
public int getItemCount() {
return expectedItems.size();
}
public int getItemsLeft() {
return expectedItems.size() - acceptedCount;
}
public Boolean acceptItem(T item) {
// is acceptItem called after sequence is completed
if (acceptedCount >= expectedItems.size()) {
return false;
}
// is the number input by the user correct
if (! expectedItems.get(acceptedCount).equals(item)) {
// if they are not the same, we indicate this by returning false object
return Boolean.FALSE;
}
acceptedCount++; // correct number, so increment counter
// is this the last number
if (acceptedCount == expectedItems.size()) {
// return true object
return Boolean.TRUE;
}
// otherwise return null, indicating correct value, but not finished
return null;
}
} |
Her har vi lagt til to ekstra metoder, for å gjøre klassen litt mer fleksibel og enklere å bruke:
- nextItems(int)-metoden gir muligheten til å utvide sekvensen med flere elementer om gangen
- konstruktøren Memory(T...) bruker såkalte varargs og gjør det enklere å lage et Memory-objekt med et spesifikt sett mulige verdier