TDT4100/oppgavetekster/oving4/Person.md

150 lines
5.8 KiB
Markdown

# Objektstrukturer - Person-oppgave
Denne oppgaven handler om en `Person`-klasse med en [1-n-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner)
med rollene *children* og *mother*/*father* til samme klasse (altså barn-mor/far-forhold) og det å sikre konsistens, slik at foreldre og barn
er korrekt knyttet sammen.
En viktig del av det å implementere assosiasjoner er å sikre konsistens, dvs. at objekter i hver ende av en kobling refererer korrekt
til hverandre. Et eksempel på dette for [1-n-assosiasjoner](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner) er
foreldreskap, hvor foreldre og barn er koblet til samme i et slektstre. I denne oppgaven skal en `Person`-klasse implementeres og det å
legge til (og fjerne) barn håndteres korrekt, som illustrert nedenfor.
`Person`-klassen skal inneholde informasjon om *navn* (en String) og *kjønn* (en char, 'F' eller 'M'), som bare skal kunne settes i
konstruktøren, og *mor*, *far* og *barn*, som er andre Person-objekter. Navnet er ikke viktig for oppførselen, men er grei å ha med
i en `toString()`-metode, for å skille Person-objektene fra hverandre. `Person`-klassen skal ha følgende metoder for å lese tilstanden:
* `getName()` - returnerer navnet knyttet til dette Person-objektet
* `getGender()` - returnerer tegnet som representerer kjønnet, enten 'F' eller 'M'
* `getMother()` - returnerer Person-objektet som er moren, evt. `null`
* `getFather()` - returnerer Person-objektet som er faren, evt. `null`
* `getChildCount()` - returnerer antall barn dette Person-objektet har
* `getChild(int n)` - returnerer barn nr. n (altså et Person-objekt), evt. utløser (et passende) unntak om `n` er for stor (eller liten)
`Person`-klassen har to sett med endringsmetoder, knyttet til de to rollene i hver ende av *children*-*mother*/*father*-assosiasjonen.
Fra *children*-perspektivet har vi følgende to metoder:
* `addChild(Person)` - oppretter en kobling til et barn (et annet Person-objekt). Dersom Person-objektet som metoden kalles på, er
en *kvinne*, så skal denne bli barnets *mor*, og motsatt, dersom Person-objektet som metoden kalles på, er en *mann*, så skal denne
bli barnets *far*.
* `removeChild(Person)` - fjerner en kobling til et barn (et annet Person-objekt). Dersom Person-objektet som metoden kalles på, er *moren*
til argumentet, så skal *mother*-koblingen fjernes, og motsatt, dersom Person-objektet som metoden kalles på, er argumentets *far*, så skal
*father*-koblingen fjernes.
Fra *mother*/*father*-perspektivet har vi følgende to metoder:
* `setMother(Person)` - setter argumentet (en kvinne) som *moren* til Person-objektet som metoden kalles på. Argumentet får samtidig
registrert Person-objektet som metoden kalles på, som sitt *barn*.
* `setFather(Person)` - setter argumentet (en mann) som *faren* til Person-objektet som metoden kalles på. Argumentet får samtidig
registrert Person-objektet som metoden kalles på, som sitt *barn*.
Det som er verd å merke seg er at begge sett med metoder, `addChild`/`removeChild` og `setMother`/`setFather`, må ha logikk
som håndterer koblingen den andre veien, så `addChild`/`removeChild` må kalle `setMother`/`setFather` og omvendt, eller ha kode
med tilsvarende effekt. Dette kan være nokså fiklete, fordi en både må sikre konsistens og unngå uendelig nøstede kall
(inntil du får StackOverflowException).
Listen og figurene under illustrerer de fem tilfellene som må kunne håndteres, og som testes av testene det er lenket til.
### 1. Opprettelse av koblinger med `addChild`
**Kall**:
marit.addChild(jens)
hallvard.addChild(jens)
(Dette har samme effekt som kallene under punkt 2.)
**Før kall**:
![](img/person1.png)
**Etter kall**:
![](img/person2.png)
### 2. Opprettelse av koblinger med `setMother` og `setFather`
**Kall**:
jens.setMother(marit)
jens.setFather(hallvard)
(Dette har samme effekt som kallene under punkt 1.)
**Før kall**:
![](img/person1.png)
**Etter kall**:
![](img/person2.png)
### 3. Fjerning av koblinger med `removeChild`
**Kall**:
marit.removeChild(jens)
hallvard.removeChild(jens)
(Dette har samme effekt som kallene under punkt 4.)
**Før kall**:
![](img/person2.png)
**Etter kall**:
![](img/person1.png)
### 4. Fjerning av koblinger med `setMother` og `setFather`
**Kall**:
jens.setMother(null)
jens.setFather(null)
(Dette har samme effekt som kallene under punkt 3.)
**Før kall**:
![](img/person2.png)
**Etter kall**:
![](img/person1.png)
### 5. Fjerning og oppretting av kobling med `setMother` og `setFather`, en slags "adoption"
**Kall**:
jens.setFather(torkel)
jens.setMother(jorunn)
**Før kall**:
![](img/person3.png)
**Etter kall**:
![](img/person4.png)
## Oppgaven
Oppgaven er delt i to trinn, den første håndterer *children*- og *mother*/*father*-rollen isolert og uten krav om konsistens,
mens det andre skal sikre konsistens.
### Del 1
* Implementer `addChild`- og `removeChild`-metodene slik at `getChildCount`- og `getChild`-metodene virker som forventet.
Disse metodene håndterer altså kun *children*-rollen.
* Implementer `setMother`- og `setFather`-metodene slik at `getMother`- og `getFather`-metodene virker som forventet.
Disse metodene håndteres altså kun *mother*/*father*-rollen.
Test metodene ved å koble opp Person-objekter tilsvarende din egen familie. Du blir nødt til å bruke de tre metodene `addChild`, `setMother`
og `setFather`. Prøv å få med minst tre generasjoner.
### Del 2
Utvid metodene til å sikre konsistens. Test at det fortsatt virker å koble opp din egen familie, denne gangen ved å bare bruke
`addChild` og ved å bare bruke `setMother` og `setFather`. Kjør JUnit-testene som hører til oppgaven.
Testkode for denne oppgaven finner du her: [objectstructures/PersonTest.java](../../src/test/java/objectstructures/PersonTest.java).