Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Expand
titleDel 3 - Klasser (35%)

For å kunne stave ord over en dårlig kommunikasjonslinje er det vanlig å bruke et ord-basert alfabet, hvor første bokstav i ordet tilsvarer bokstaven som sies. Nato har standardisert dette i sitt fonetiske alfabet, som begynner med ”alfa”, ”bravo”, ”charlie”, ”delta”, ”echo”, ”foxtrot”. Dersom ordet ”java” staves med dette alfabetet blir det ”juliet alfa viktor alfa”.

Du skal implementere en klasse RadioAlphabet for å konvertere bokstaver og ord vha. denne teknikken. Du velger selv hvordan alfabetet representeres, f.eks. kan en String, String-tabell, List eller Map brukes for å representere alfabetet. Merk at det kan være lurt å lese gjennom hele oppgaven og ta en titt på oppgave 5, før du velger mellom en av disse og evt. andre alternativer.

Oppgave a)

Først skal du implementere én konstruktør og to metoder, som følger:

  • Konstruktøren skal ta inn ordene i alfabetet som en String og en String som inneholder skilletegnet. Merk at ordene trenger ikke være i alfabetisk rekkefølge, fordi første bokstav i ordet avgjør hvilken bokstav det tilsvarer. F.eks. skal new RadioAlphabet(”alfa-bravo-delta-charlie”, ”-”) gi et objekt som kan oversette bokstavene ’a’-’d’ med Nato-alfabetet. Alfabetet skal kun kunne inneholde ord for bokstaver (altså ikke tall og skilletegn).

  • Metoden String convert(char) skal returnere ordet som tilsvarer bokstaven som gis som parameter, eller null dersom bokstaven ikke kan oversettes. F.eks. skal convert(’a’) returnere ”alfa” og convert(’!’) skal returnere null.

  • Metoden String convert(String) skal returnere sekvensen av ord (med mellomrom mellom) i en String som tilsvarer ordet som gis som parameter. Bokstaver som ikke kan oversettes skal ignoreres. F.eks. skal convert(”*abba*”) returnere ”alfa bravo bravo alfa”.

Expand
titleLF

Det enkleste er å bruke List/ArrayList eller en Map/HashMap, siden disse har dynamisk størrelse. En Map gir kanskje aller enklest kode (litt mer kompleks konstruktør), men her vises en løsning som bruker List. Lista inneholder kun alfabet-ordene og koden utnytter at første bokstav i ordet er nettopp bokstaven som ordet er oversettelsen av.

Det gis 4 poeng for en fornuftig datarepresentasjon og helhetlig bruk av den, 2 for konstruktøren, 1 for hver av converts og convert(char) og 2 for convert(String). Det trekkes poeng dersom kode gjentas, f.eks. for finne posisjonen til et ord i tabellen/lista.

Split bør være kjent og gir en enklere konstruktør. Arrays.asList er mindre kjent, så en løkke for å legge til ord er greit. Merk hvordan pos-metoden, som finner hvor ordet for en bokstav er, brukes av flere av de andre metodene. I convert-metoden kan man bruke String +=, StringBuilder.append eller StringBuffer.append. Merk at det kreves egen logikk for å unngå unødvendige blanke tegn, foran inni eller i slutten av String’en. Siden en bokstav ikke nødvendigvis kan oversettes, er det ikke nok å sjekke indeksen for å avgjøre om det skal legges inn mellomrom. Her løses det delvis med en if, delvis med String.trim.

Code Block
public class RadioAlphabet {
	
	private List<String> alphabet;
	
	public RadioAlphabet(String alphabet, String separator) {
		this.alphabet = new ArrayList<String>(Arrays.asList(alphabet.split(separator)));
	}
	private int pos(char c) {
		for (int i = 0; i < alphabet.size(); i++) {
			if (alphabet.get(i).charAt(0) == c) {
				return i;
			}
		}
		return -1;
	}
	
	public boolean converts(char c) {
		return pos(c) >= 0;
	}
	
	public String convert(char c) {
		int pos = pos(c);
		return pos >= 0 ? alphabet.get(pos) : null;
	}
	
	public String convert(String word) {
		StringBuffer /* or StringBuilder */ buffer = new StringBuffer();
		for (int i = 0; i < word.length(); i++) {
			String converted = convert(word.charAt(i));
			if (converted != null) {
				buffer.append(converted);
				buffer.append(' ');
			}
		}
		return buffer.toString().trim();
	}
}
Oppgave b)

Hvorfor kan ikke disse metodene være deklarert med static-modifikatoren?

Expand
titleLF
 Med static-modifikatoren vil metodene ikke kunne referere til et bestemt RadioAlphabet-objekt. En kan for så vidt gjøre alfabetet static også, men da vil en bare kunne ha ett globalt alfabet.
Oppgave c)

Hva kalles det når metoder i samme klasse (som convert-metodene) har samme navn? Hva er regelen for å skille dem fra hverandre/velge mellom dem, når de brukes/kalles?

Expand
titleLF

At metoder i en klasse kan ha samme navn kalles ”overloading”. For å avgjøre hvilken som skal kalles, brukes de deklarerte typene til argumentene (ikke returverdien). Merk at dette er noe annet enn polymorfi, som handler om at subklasser kan ha ulike implementasjoner av metoder definert i en felles superklasse.

Oppgave d)

Utvid klassen med metodene setWord(String) som endrer ordet som brukes for en bestemt bokstav (første bokstav i ordet), og removeWord(char) som fjerner ordet for en bestemt bokstav fra alfabetet. Merk at setWord skal kun kunne endre ordet for en bokstav som allerede finnes i alfabetet.

Expand
titleLF

 Her brukes pos-metoden som også ble brukt av convert(char). Merk at setWord ikke skal legge inn et nytt ord, kun erstatte et eksistererende.

Code Block
themeEclipse
languagejava
 public void setWord(String word) {
	int pos = pos(word.charAt(0));
	if (pos >= 0) {
		alphabet.set(pos, word);
	}
}
public void removeWord(char c) {
	int pos = pos(c);
	if (pos >= 0) {
		alphabet.remove(pos);
	}
}

Det gis 5 poeng for riktige løsninger. Det trekkes 1 poeng dersom setWord legger til ord. Det trekkes poeng dersom kode gjentas, f.eks. for finne posisjonen til et ord i tabellen/lista.

Lenke til løsningskode: RadioAlphabet.java

 

Expand
titleDel 4 - Testing (10%)
 Oppgave a)

 Skriv en JUnit-testklasse som tester begge convert-metodene i RadioAlphabet-klassen. Du kan anta at konstruktøren virker. Vi er ikke så nøye på detaljene, bare den generelle testeteknikken er riktig.

Expand
titleLF

Det sentrale her er å rigge opp et test-objekt og å bruke relevante testdata, inkludert bokstaver som ikke har noen oversettelse og ikke-bokstaver. Som nevnt er vi ikke opptatt at JUnit-spesifikke detaljer, men dersom en ikke bruker JUnit-metoder, så bør en selv definere egne assert-aktige hjelpemetoder.

Code Block
themeEclipse
languagejava
private RadioAlphabet radioAlphabet;
@Override
protected void setUp() throws Exception {
	// TODO Auto-generated method stub
	super.setUp();
	radioAlphabet = new RadioAlphabet(alphabet, "-");
}
public void testConvertChar() {
	assertEquals("alfa", radioAlphabet.convert('a'));
	assertEquals("hotel", radioAlphabet.convert('h'));
	assertEquals("zulu", radioAlphabet.convert('z'));
	assertNull(radioAlphabet.convert('!'));
}
public void testConvertString() {
	assertEquals("hotel alfa lima lima victor alfa romeo delta", radioAlphabet.convert("hallvard"));
	assertEquals("hotel alfa lima alfa lima", radioAlphabet.convert("!h!a!l!a!l?"));
	radioAlphabet.setWord("åring");
	assertEquals("hotel victor alfa romeo delta", radioAlphabet.convert("håvard"));
}
Oppgave b)

Forklar hvordan du vil teste setWord- og removeWord-metodene i RadioAlphabet-klassen vha. JUnit-teknikken. Hva er en vesentlig (kompliserende) forskjell mellom å teste disse metodene og å teste convert-metodene?

Expand
titleLF

Det vesentlige her er at en må teste oppførselen både før og etter bruken av endringsmetoden, i praksis at convert fungerer ulikt før og etter bruk av setWord og removeWord. Det kan synes som om det kun er nødvendig å teste oppførselen etterpå, fordi en tross alt har andre tester for oppførsel også. Men det er riktig å test både før og etter, for at testen skal dekke tilfellet alene.

Code Block
themeEclipse
languagejava
public void testSetWord() {
	assertEquals("hotel alfa lima lima victor alfa romeo delta", radioAlphabet.convert("hallvard"));
	radioAlphabet.setWord("lala");
	assertEquals("hotel alfa lala lala victor alfa romeo delta", radioAlphabet.convert("hallvard"));
}
public void testRemoveWord() {
	assertEquals("hotel alfa lima lima victor alfa romeo delta", radioAlphabet.convert("hallvard"));
	radioAlphabet.removeWord('l');
	assertEquals("hotel alfa victor alfa romeo delta", radioAlphabet.convert("hallvard"));
}

Lenke til løsningskode: RadioAlphabetTest.java

 

Expand
titleDel 5 - Arv og observerbarhet (25%)
Oppgave a)

Morse-alfabetet er et alfabet hvor bokstaver (og andre tegn) representeres med sekvenser av prikker og streker. F.eks. skrives det internasjonale nødsignalet ”SOS” som ”... --- ...” fordi ’s’ skrives som ”...” og ’o’ som ”---”. Du skal lage en subklasse av RadioAlphabet-klassen kalt MorseAlphabet, som støtter dette alfabetet. F.eks. skal new MorseAlphabet() gi et ferdig-initialisert objekt som kan konvertere ”sos” til ”... --- ...” og ”test” til ”- . ... -". convert-metodene og removeWord skal virke som spesifisert i del 3. setWord-metoden må nødvendigvis endres slik at den tar inn tegnet som første argument og ordet som andre. 

Det er et mål å utnytte arv slik at det blir mest mulig gjenbruk av kode, f.eks. ved at en eller flere av de arvede metodene ikke trenger å redefineres. Forklar med tekst og kode hvordan du vil gjøre dette. Forklar om nødvendig hvordan du må endre på RadioAlphabet-klassen for å få det til, men merk at den må fortsatt virke slik det er beskrevet i del 3.

Expand
titleLF

Besvarelser vurderes ut fra korrekthet og grad av gjenbruk. Noen løsninger på del 3, vil egne seg bedre til gjenbruk enn andre, så vi må delvis vurdere ut fra det vi oppfatter som potensialet for gjenbruk. Det teller positivt at en reflekterer over om en kunne økt graden av gjenbruk, dersom en hadde skrevet om RadioAlphabet slik og slik, selv om en ikke har hatt tid til å gjøre det.

Generelt så er det best om en kan gjenbruke logikk som allerede er public. protected-metoder foretrekkes fremfor protected-felt.

Med vår løsning blir det lite ekstra kode, siden den eksisterende representasjonen med forbokstav og ord i ett kan også brukes for morsealfabetet. Hvert element i lista vil være tegnet foran prikkene og strekene, f.eks. ”s...” og ”o---”. Trikset er å utnytte superklassens metoder, ved å justere på argument/returverdi. convert(char) kaller super-metoden og fjerner første bokstav. Den nye setWord-metoden kaller superklassens setWord-metode med tegnet lagt foran ordet. Convert(String) og remove-metoden arves i sin helhet.

Code Block
themeEclipse
languagejava
public class MorseAlphabet extends RadioAlphabet {
	private final static String morseAlphabet = "s... o--- e. t-";
	public MorseAlphabet() {
		super(morseAlphabet, " ");
	}
	public String convert(char c) {
		String word = super.convert(c);
		return (word != null ? word.substring(1) : null);
	}
	public void setWord(char c, String word) {
		super.setWord(c + word);
	}
}

Lenke til løsningskode: MorseAlphabet.java (MorseRadioAlphabetTest.java)

 

Oppgave b)

 Du skal gjøre RadioAlphabet-klassen (og dermed også subklassen) såkalt ”observerbar”, vha. følgende lyttergrensesnitt:

 public interface RadioAlphabetListener {
public void radioAlphabetChanged(RadioAlphabet, char);
}

Dersom en RadioAlphabet-instans endres, skal altså metoden radioAlphabetChanged kalles på alle lytterne, med RadioAlphabet-instansen som første argument og tegnet viss’ ord ble endret/fjernet som det andre. Beskriv hvordan RadioAlphabet og evt. MorseAlphabet må endres for å implementere denne oppførselen.

Expand
titleLF

Observerbarhet krever

  1. at en holder styr på lytterne og
  2. at alle endringsmetoder kaller lytternes lyttermetode.

Med vår løsning trenger en faktisk ikke å endre MorseAlphabet, siden metodene for 1) arves og all endring 2) skjer gjennom arvede metoder.

  1. Holder styr på lytterne

    Code Block
    themeEclipse
    languagejava
    private List<RadioAlphabetListener> listeners = new ArrayList<RadioAlphabetListener>();
    public void addRadioAlphabetListener(RadioAlphabetListener listener) {
    	listeners.add(listener);
    }
    public void removeRadioAlphabetListener(RadioAlphabetListener listener) {
    	listeners.remove(listener);
    }
  2.  Alle endringsmetoder kaller lytternes lyttermetode:

    Code Block
    themeEclipse
    languagejava
    protected void fireRadioAlphabet(char c) {
    	for (RadioAlphabetListener listener : listeners) {
    		listener.radioAlphabetChanged(this, c);
    	}
    }
    public void setWord(String word) {
    	int pos = pos(word.charAt(0));
    	if (pos >= 0) {
    		alphabet.set(pos, word);
    		// added
    		fireRadioAlphabet(word.charAt(0));
    	}
    }
    public void removeWord(char c) {
    	int pos = pos(c);
    	if (pos >= 0) {
    		alphabet.remove(pos);
    		// added
    		fireRadioAlphabet(c);
    	}
    }

Lenke til løsningskode: RadioAlphabetListener.java, RadioAlphabet.java (RadioAlphabetTest.java)