Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Expand
titleDel 1 - Gender- og Person-klassene (30%35%)

Gender-klassen (vedlegg 1) representerer kjønnet til en person, i denne oppgaven begrenset til mann og kvinne. Klassen skal kodes slik at det ikke skal være mulig å ha andre Gender-objekter enn disse to kjønnene. En Gender-instans har et forklarende ord/navn knyttet til seg, som settes når instansen lages, og som ikke skal være direkte tilgjengelig fra andre klasser.

Oppgave a)

Hvilke modifikatorer bør stå foran deklarasjonen av label-feltet og konstruktøren? Begrunn svaret. 

Expand
titleLF

  Begge disse bør være markert som private, siden de ikke skal være tilgjengelig utenfor klassen. label bør også være final, siden den ikke skal kunne endres.

Oppgave b)

Hvordan er klassen kodet så det forklarende ordet/navnet vises ved utskrift, f.eks. med System.out.println(...)?

Expand
titleLF
Ved utskrift så brukes implisitt toString()-metoden, som er implementert og returnerer label.
Oppgave c)

Fullfør valueOf-metoden.

Expand
titleLF

Her er det viktig å returnere et eksisterende Gender-objekt, ved å sammenligne argumentet med label-verdien i de to konstantene MALE og FEMALE. En kan også bruke switch, som i Java 8 virker med String-objekter. valueOf ligner på Integer.valueOf og Double.valueOf og må være static, siden det er unaturlig og unødvendig å kalle den på en eksisterende instans.

Code Block
public static Gender valueOf(String label) {
   if (MALE.label.equals(label)) {
      return MALE;
   } else if (FEMALE.label.equals(label)) {
      return FEMALE;
   }
   return null;
}

Person-klassen (vedlegg 1) representerer en person, med et navn (String) og et kjønn (Gender). Navnet settes kun ved opprettelsen, mens kjønnet kan settes når som helst.

For å håndtere familieforhold så har et Person-objekt data om mor, far og barn. addChild-metoden brukes for å knytte et barn til en forelder, og det er ikke spesifisert andre metoder som endrer barn-forelder-koblingen. addChild både registrerer barnet og setter barnets kobling til mor eller far, avhengig av kjønnet til forelderen. Ta f.eks. kallet chris.addChild(pat), hvor pat blir registert som barnet til chris. Hvis chris er mann, så blir han registrert som pat sin far, mens hvis chris er kvinne, så blir hun registrert som pat sin mor.

Oppgave d)

Skriv felt, metoder og konstruktør for innkapsling av navn, kjønn, mor, og far. Bruk name, gender, mother og father som grunnlag for navngiving.

Expand
titleLF

Her er litt av poenget at en må ha en konstruktør som setter navnet og utelate set-metoder for name, mother og father.

Code Block
private final String name;
private Gender gender = null;
private Person father, mother;

public Person(String name) {
   this.name = name;
}


public String getName() {
   return name;
}

public Gender getGender() {
   return gender;
}

public void setGender(Gender gender) {
   this.gender = gender;
}

public Person getMother() {
   return mother;
}

public Person getFather() {
   return father;
}
Oppgave e)

Implementer getChildCount, hasChild og getChildren, med nødvendig(e) felt.

Expand
titleLF

Her må en ha riktig deklarasjon av feltet (helst Collection, sekundært List), og huske å initialisere feltet. getChildren må passe på å returnere en ny Collection, så annen kode ikke får tilgang til interne data. En må ta høyde for at gender kan være null. En kan gjerne bruke Stream-teknikken.

 

Code Block
private Collection<Person> children = new ArrayList<>();

public int getChildCount() {
   return children.size();
}

public boolean hasChild(Person child) {
   return children.contains(child);
}

// alternativ

public boolean hasChild(Person child) {
   return child.getFather() == this || child.getMother() == this;
}

public Collection<Person> getChildren(Gender gender) {
   Collection<Person> result = new ArrayList<>();
   for (Person child : children) {
      if (gender == null || child.getGender() == gender) {
         result.add(child);
      }
   }
   return result;
}
Oppgave f)

Som en del av innkapslingen av barn-koblingen, så har vi valgt å la Person implementere Iterable<Person>-grensesnittet. Hva betyr dette for koden en kan skrive for å gå gjennom barna til en Person? Implementer metoden(e) som er påkrevd av dette grensesnittet.

Expand
titleLF

Hvis en klasse implementerer Iterable, så kan referanser til denne klassen brukes på høyresiden av kolonet i en for-each-løkke, f.eks. for (Person child : person). Se også getChildren-koden over.

Et (litt mindre relevant, og ikke påkrevd) alternativ er Iterable.forEach(Consumer<Person>). Dette er en såkalt default-metode (kom ikke frem i vedlegget) som en får gratis når en implementerer Iterable.

Code Block
@Override
public Iterator<Person> iterator() {
   return children.iterator();
}
Oppgave g)

Implementer addChild-metoden, basert på kravene beskrevet over og i vedlegg 1, og kravene som implisitt testes av testAddChild i PersonTest-klassen (vedlegg 1).

Expand
titleLF

 

 

Code Block
public void addChild(Person child) {
   if (getGender() == Gender.MALE) {
      if (child.father != null) {
         child.father.children.remove(child);
      }
      child.father = this;
   } else if (getGender() == Gender.FEMALE) {
      if (child.mother != null) {
         child.mother.children.remove(child);
      }
      child.mother = this;
   }
   children.add(child);
}

Oppgave h)

Nederst i testmetoden testAddChild er to linjer marker med // ??? Hvilke tilfeller eller problemer er det som kan testes på de to punktene i koden? Hvordan oppfører din kode seg, og hva mener du er riktig oppførsel i de to tilfellene? (Du trenger ikke rette på koden din)

Expand
titleLF

Her er det vanskelig å komme med en fasit, men poenget er å vurdere oppførselen til egen kode for tilfeller en ikke har tenkt på. En bør kunne beskrive de to tilfellene og forstå hva som faktisk vil skje i egen kode.

I det første tilfellet skifter far først kjønn, før hun på ny får lagt til barnet. Koden over vil fjerne koblingen til og fra den eksisterende moren, og så etablere en tilsvarende kobling med den nye moren, som også er den eksisterende faren. Hun ender opp med å være far og mor til samme barn, og dette barnet vil finnes dobbelt opp i children-lista. Her ville nok det riktigste være at den eksisterende far-koblingen også ble fjernet, før mor-barn-koblingen ble etablert.

I det andre tilfellet så legges faren til som barn av eget barn, som gir en sirkulær struktur. Dette bør det være en sjekk for, for mange algoritmer vil da ende opp i evig løkke.

Expand
titleDel 2 - <tema> (xx%)Family-klassen og IO (25%)

Family-klassen (vedlegg 1) holder rede på personer i en familie, med metoder for å legge til familiemedlemmer (Person-objekter), slå opp personer på navn, lagre familiemedlemmene og lese dem inn igjen.

Oppgave a)

Skriv metodene addMember og findMember (og definer nødvendige felt), som henholdsvis legger en Person til som familiemedlem og finner et familiemedlem med et angitt navn.

Expand
titleLF

Samme krav til members-lista (navnet er ikke nøye) som til Person.children. En trenger ikke sørge for at alle barn av personer som legges til, også legges til. Dette kan en anta gjøres utenfra. Må bruke equals for å sammenligne String-objekter.

Code Block
private Collection<Person> members = new ArrayList<>();

public void addMember(Person person) {
   members.add(person);
}

public Person findMember(String name) {
   for (Person person : members) {
      if (person.getName().equals(name)) {
         return person;
      }
   }
   return null;
}

I vedlegg 2 beskrives et tekstformat for data om personene i en familie, inkludert foreldre-barn-koblingene.

Oppgave b)

I vedlegg 1 er det med en hjelpemetode tokenize, som kan være nyttig ved innlesing og som kan antas ferdig implementert. Hvilke(n) modifikator(er) burde den ha? Begrunn svaret!

Expand
titleLF
En slik hjelpemetode bør for det første være markert som private, siden det ikke er naturlig at dette er en tjeneste som tilbys andre klasser. For det andre bør den være markert som static, siden den ikke bruker (leser eller endrer) tilstanden til noe Family-objekt. Eneste grunn til at den ikke skal være static, er hvis en subklasse av Family har behov for å redefinere den, og det er ikke aktuelt her.
Oppgave c)

Skriv metodene save og load, som støtter dette tekstformatet. Begrunn hvordan du velger å behandle problemet med unntak.

Expand
titleLF

Det viktigste med save-metoden er at den først skriver ut alle linjer av type 1, altså den person-informasjonen som er nødvendig for å lage Person-objektene før foreldre-barn-koblingen etableres. Vi velger å lage en PrintWriter rundt OutputStream-en vi får inn, for å muliggjøre bruke av print og println. Vi kunne brukt en PrintStream, men en Writer anbefales jo for tekst (trekker ikke for bruk av PrintStream). Så skrives alle linjene av type 2 ut. Derfor blir det to iterasjoner over alle medlemmene. Navn får anførselstegn (”) rundt (merk måten ” inkluderes i en String). Her sjekkes det om en person har barn (kan gjøre på mange måter), så det ikke blir linjer med en forelder, men det er strengt tatt ikke definert som et krav (det står ”sequence of names”, og en sekvens kan jo ha bare ett element). Hvis en har linjer med bare én forelder, så er det viktig at load-metoden håndterer det riktig. Det er vanlig at den som setter opp en OutputStream også lukker den, og derfor avslutter vi ikke med pw.close(). Vi avslutter imidlertid med pw.flush() for å sikre at all vår output sendes ut med en gang (trekker ikke for manglende bruk av close()/flush()).

load-metoden klassifiserer hver linje som en av de tre typene ved å først sjekke om den er tom eller starter med # (type 3) og så sjekker om første token i en linje er en gyldig Gender (type 1). Ellers er den av type 2. Her gjøres det ingen sjekk på om formatet er korrekt, f.eks. om et barn i en linje av type 2 faktisk er registrert som familiemedlem. Det er kanskje litt uklart hvorvidt og evt. hvordan tokenize håndterer #, så det er greit at den brukes før en sjekker for linjer av type 3.

Unntak håndteres ikke av metodene, så de må deklareres med throws. Det er naturlig å bruke IOException, for den utløses ved bruk av InputStream og OutputStream. En kunne brukt Exception or å markere (at vi er klar over) at det er mye som kan gå galt, men det anbefales å bruke den mest spesifikke typen. Vi kunne fanget opp og ignorert unntak, men det kan lett maskere feil vi ønsker å avdekke.

Code Block
private void outputQuotedName(Person person, PrintWriter pw) {
   pw.print("\"" + person.getName() + "\"");       
}

public void save(OutputStream out) throws IOException {
   PrintWriter pw = new PrintWriter(out);
   pw.println("# all persons");
   for (Person person : members) {
      pw.print(person.getGender());
      pw.print(" ");
      outputQuotedName(person, pw);
      pw.println();
   }
   pw.println();
   pw.println("# all mother/father-child relations");
   for (Person person : members) {
      if (person.iterator().hasNext()) {
         outputQuotedName(person, pw);
         for (Person child : person) {
            pw.print(" ");
            outputQuotedName(child, pw);
         }
         pw.println();
      }
   }
   pw.flush();
}

public void load(InputStream in) throws IOException {
   Scanner scanner = new Scanner(in);
   while (scanner.hasNextLine()) {
      String line = scanner.nextLine();
      if (line.trim().length() == 0 || line.startsWith("#")) {
         continue;
      }
      List<String> tokens = tokenize(line);
      Gender gender = Gender.valueOf(tokens.get(0));
      if (gender != null) {
         // type 1 line
         Person person = new Person(tokens.get(1));
         person.setGender(gender);
         addMember(person);
      } else {
         // type 2 line
         Person person = findMember(tokens.get(0));
         for (int i = 1; i < tokens.size(); i++) {
            Person child = findMember(tokens.get(i));
            person.addChild(child);
         }
      }
   }
   scanner.close();
}
Oppgave a)

 

Expand
titleLF

 

Oppgave b)

 

Expand
titleLF
 
Oppgave c)

 

Expand
titleLF
 


Oppgave d)

 

Expand
titleLF
 
Oppgave e)

 

Expand
titleLF
 
Oppgave f)

 

Expand
titleLF
 

...