You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

Oppgave a)

  En kan kategorisere innkapslingsmetoder som enten lese- eller endringsmetoder. Hva er den viktigste oppgaven til endringsmetodene, bortsett fra å utføre selve endringen?

Den viktigste oppgaven er å sjekke (validere) om den nye verdien (eller nye verdiene) er lovlige/gyldige, før de evt. endres, f.eks. at et navn kun inneholder bokstaver og mellomrom.

Oppgave b)

  Gitt en Date-klasse med metoder getDay() for å lese datoens dag (1-31), getMonth() for måned (1-12) og getYear() for år (0-99). Skriv kode for en Person-klasse, med felt og innkapslingsmetoder for fødselsdato og kjønn. Kjønn skal kun kunne settes ved oppretting av Person-objektet, mens det for fødselsdatoen ikke er noen slik begrensning.

 Her er poenget å velge hvilke konstruktører og metoder som naturlig hører med til innkapslingen, inkludert navn og typer. Data som kun skal kunne settes ved oppretting krever en konstruktør med tilsvarende parameter og en get-metode. Andre data trenger både get- og set-metoder. Set-metoden for kjønn bør validere verdien hvis en bruker ”ubegrensede” typer som String eller int.

 

 

       public static String MALE_GENDER = "male", FEMALE_GENDER = "female";
       private String gender;
       private Date dateOfBirth;

       public Person(String gender) {
              if (gender != MALE_GENDER && gender != FEMALE_GENDER) {
                      throw new IllegalArgumentException(gender + " is not a legal gender");
              }
              this.gender = gender;
       }

       public String getGender() {
              return gender;
       }    
       public Date getDateOfBirth() {
              return dateOfBirth;
       }
       public void setDateOfBirth(Date dateOfBirth) {
              this.dateOfBirth = dateOfBirth;
       }
Oppgave c)

Skriv kode for å lagre og innkapsle personnummer i Person-objekter. Du kan anta at fødselsdato og kjønn allerede er satt. Et personnummer består grovt sett av fødselsdatoen, et (vilkårlig) løpenummer og to kontrollsifre. Kontrollsifrene gjør det enklere å sjekke om et personnummer er ekte. Mer spesifikt er reglene for personnummer som følger:

-       Et personnummer består av 11 siffer, med følgende struktur: D1D2M1M2Y1Y2N1N2N3K1K2 (fargen illustrerer siffergruppene).

-       De seks første sifrene, D1D2M1M2Y1Y2, tilsvarer fødselsdatoens dag (1-31), måned (1-12) og år (0-99).

-       De tre neste sifrene, N1N2N3, kan antas å være vilkårlige, men N3 må være partall for kvinner og oddetall for menn.

-       De to siste sifrene, K1K2, er kontrollsifre, som hver for seg beregnes ut fra de foregående sifrene. Formellen for dem begge er 11 – (VS % 11)[1], hvor VS (veid sum) for K1 er D1*F1 + D2*F2 + … + N2*F8 + N3*F9 og VS for K2 er D1*G1 + D2*G2 + … + N3*G9 + K1*G10. F’ene og G’ene er oppgitt i tabellen under. Dersom formelen gir et ett-sifret resultat for både K1 og K2 så er personnummeret gyldig, mens gir formelen et to-sifret resultat for K1 og/eller K2, så er personnummeret ugyldig.

 

1

2

3

4

5

6

7

8

9

10

F

3

7

6

1

8

9

4

5

2

 

G

5

4

3

2

7

6

5

4

3

2

Introduser og bruk gjerne hjelpemetoder for å gjøre koden ryddigere.

 
 Her må en velge egnet datatype for personummeret. En kan forsåvidt lage en egen klasse, men det er greit å bruke talltabell, tegntabell eller String. En må også lage riktig valideringskode, som bør inneholde sjekk for lengde og at hvert element er et siffer, at fødselsdatoen stemmer, at kjønn stemmer med D3 og at kontrollsifrene er riktige. Det er naturlig å lage en hjelpemetode for formelen for kontrollsifrene evt. veid sum, siden den brukes to ganger, og ha én tabell for hvert sett med faktorer. Det kan også være nyttig med en hjelpemetode for å sjekke at to sifre stemmer med et heltall. Hjelpemetodene bør deklareres som static.

      

		private String pid;
       private static int[] factors1 = {3, 7, 6, 1, 8, 9, 4, 5, 2}, factors2 = {5, 4, 3, 2, 7, 6, 5, 4, 3, 2};
       
		private static int computeControlDigit(String digits, int[] factors) {
              int sum = 0;
              for (int i = 0; i < factors.length; i++) {
                      sum += (digits.charAt(i) - '0') * factors[i];
              }
              return 11 - (sum % 11);
       }     
       private static boolean checkDigits(String digits, int pos, int num) {
              return (num / 10 == digits.charAt(pos) - '0' && num % 10 == digits.charAt(pos + 1) - '0');
       }   
       private boolean validatePID(String pid) {
              if (pid.length() != 11) {
                     return false;
              }

              for (int i = 0; i < pid.length(); i++) {
                      if (! Character.isDigit(pid.charAt(i))) {
                             return false;
                      }
              }
              int day = dateOfBirth.getDay(), month = dateOfBirth.getMonth(), year = dateOfBirth.getYear();
              if (! (checkDigits(pid, 0, day) && checkDigits(pid, 2, month)) && checkDigits(pid, 4, year)) {
                      return false;
              }
              boolean isOdd = ((pid.charAt(8) - '0') % 2) == 1;
              if ((gender == MALE_GENDER) != isOdd) {
                      return false;
              }
              int k1 = computeControlDigit(pid, factors1), k2 = computeControlDigit(pid, factors2);
              if (k1 != pid.charAt(9) - '0' || k2 != pid.charAt(10) - '0') {
                      return false;
              }
              return true;
       }
       public String getPID() {
              return pid;
       }

       public void setPID(String pid) {
              if (! validatePID(pid)) {
                      throw new IllegalArgumentException(pid + " is not a valid PID for " + gender + " and " + dateOfBirth);
              }
              this.pid = pid;
       }

En del skrev kode for generere personumre, ikke validere. Siden dette i praksis innebar å skrive mye av den samme (type) koden, så fikk de uttelling for det også.

Oppgave d)

  Beskriv hvordan du kan bruke en såkalt checked exception for å avverge at fødselsdatoen endres etter at personnummeret er satt! Hva slags konsekvenser vil dette ha for kode som kaller endringsmetoden for fødselsdatoen?

 

En checked exception er en subklasse av Exception som ikke samtidig er en subklasse av RuntimeException. En slik Exception må deklareres vha. throws og den kallende metoden må enten håndtere unntaket med try/catch eller deklarere det med throws.

 

       public void setDateOfBirth(Date dateOfBirth) throws Exception {

              if (pid != null) {

                      throw new Exception("Cannot change date of birth after PID has been set");

              }              this.dateOfBirth = dateOfBirth;

      

Du skal implementere klasser for å representere informasjon ifm. gjennomføring av seriespill i sporter som fotball, håndball, volleyball osv. Klassen MatchResult skal representere informasjon om en kamp og dens resultatet, mens klassen LeagueTable skal representere et sett kamper og beregne serietabellen basert på kampresultatene. I del 2 skal vi begrense oss til tabeller for ferdigspilte fotballkamper. Et eksempel på to kampresultater og en tabell er vist nedenfor:

Rosenborg - Lillestrøm: 4 - 4

Lillestrøm - Rosenborg: 1 - 5

Rosenborg   4

Lillestrøm  1

Som vi ser har Rosenborg fått totalt 4 poeng, 3 poeng for seier og 1 poeng for uavgjort, mens Lillestrøm har fått 1 poeng for den ene uavgjort-kampen. Tabellen utelater informasjon om mål for og imot og hjemme- og bortekamper.

Tenk på helheten i løsningen før du går i gang med hver del. Kanskje kan det være lurt å begynne med c) før du skrive koden for a) og b).

Det kan være lurt å ha i bakhodet at i del 3 skal koden generaliseres til å håndtere sporter med andre regler for å beregne tabeller og i del 4 skal koden håndtere at kamper kan legges inn i tabellen før de er ferdigspilt. Men la ikke dette gjøre koden i del 2 unødvendig komplisert.

Oppgave a)

a) Implementer MatchResult:

-       Representasjon og innkapsling av informasjon om navnene på hjemme- og bortelagene. Navnene skal ikke kunne endres etter at et MatchResult-objekt er laget.

-       Representasjon og innkapsling av informasjon om kampresultatet, dvs. antall hjemme- og bortemål.

-       Metodene isParticipant(String participant) for å spørre om (laget) participant spiller i denne kampen, isDraw() for å spørre om resultatet ble uavgjort og isWinner(String participant) for å spørre om (laget) participant vant denne kampen.

Her må en velge datatype for lagnavnene og målene, hhv. String og ints. Det er ikke viktig hva en kaller feltene/metodenavnene, men pga. generaliseringen i oppgave 3 så har vi valgt sportnøytrale navn. En bør ha én eller flere metoder for å sette og/eller legge til mål. Her har vi valgt å ha én metode, hvor laget som får mål angis med lagnavnet. En kan også ha en metode for hvert lag.

public class MatchResult {
      
       private final String participant1, participant2;
       private int points1 = 0, points2 = 0;  

       public MatchResult(String participant1, String participant2) {
              this.participant1 = participant1;
              this.participant2 = participant2;
       }

       public String toString() {
              return participant1 + " - " + participant2 + ": " + points1 + " - " + points2;
       }
      
       public String getParticipant1() {
              return participant1;
       }

       public String getParticipant2() {
              return participant2;
       }

       public boolean isParticipant(String participant) {
              return participant.equals(participant1) || participant.equals(participant2);
       }
       public int getPoints(String participant) {
              if (participant.equals(participant1)) {
                      return points1;
              } else if (participant.equals(participant2)) {
                      return points2;
              } else {
                      return -1;
              }
       }
       public boolean isWinner(String participant) {
              return (participant.equals(participant1) && points1 > points2) || (participant.equals(participant2) && points2 > points1);
       }

       public boolean isDraw() {
              return points1 == points2;
       }

       public void addPoints(String participant, int points) {
              if (participant.equals(participant1)) {
                      points1 += points;
              } else if (participant.equals(participant2)) {
                      points2 += points;
              }
       }
} 

Mange skrev kode for tabellpoeng i denne klassen (3 poeng for seier, 1 for uavgjort), og det måtte vi nesten godta, selv om oppgaven hadde lagt opp til at det var logikk som skulle være i tabellklassen. 

Oppgave b)

Implementer LeagueTable:

-       Representasjon av (navnene til) alle lagene. Navnene skal ikke kunne endres etter at LeagueTable-objektet er laget.

-       Representasjon av informasjon om kampresultater, dvs. MatchResult-objekter. Det skal kun være lov å legge inn nye kamper, ikke fjerne dem. Det skal ikke være lov å legge inn kamper for lag som ikke er med i tabellen.

-       Metoden getParticipantPoints(MatchResult matchResult, String participant) som returnerer antall poeng som (laget) participant fikk for resultatet matchResult. Ta høyde for at participant faktisk ikke er med i matchResult.

Tabellpoeng summeres basert på kampresultatene, og tabellen sorteres etter antall poeng, med laget med flest poeng øverst. Når et kampresultat legges inn, så skal tabellen oppdateres.

Merk at du antageligvis vil trenge en klasse for å representere hver rad i tabellen, f.eks. kalt LeagueTableRow, som lagrer data om et lag og summen av poengene laget har fått for seier og uavgjort i sine kamper. Denne klassen kan implementere relevante grensesnitt ifm. sortering.

 

Nedenfor er en rett frem implementasjon av tabellraden. Denne implementerer Comparable-grensesnittet for å støtte sortering med Java sin sort-metode og har derfor metoden compareTo. Merk at logikken er slik at objektet som skal sorteres som først i en liste logisk sett må være minst og derfor returnere < 0 i sammenligningen. Dette er såpass subtilt at vi ikke er nøye på det i vurderingen.

 

 

public class LeagueTableRow implements Comparable<LeagueTableRow> {
       private String participant;

       public LeagueTableRow(String participant) {
              this.participant = participant;
       }

       public String getParticipant() {
              return participant;
       }
       private int points = 0;
       public int getPoints() {
              return points;
       } 
       public void addPoints(int points) {
              this.points += points;
       }

       // Comparable<LeagueTableRow>  
       public int compareTo(LeagueTableRow other) {
              return other.points - points;
       }
}


Tabell-klassen må ha en konstruktør som tar inn navnene til lagene. Det er naturlig å opprette én tabellrad for hvert lag. En trenger ikke å huske navnene, siden de ligger i radene. En må ha en metode for å legge til et resultat som oppdaterer antall poeng for hvert lag og sorterer i etterkant. Innkapsling av radene, dvs. at en har metoder for å lese radene, er ikke så sentralt. Det kan gjøres på ulike måter og her gjør vi det enkelt med en iterator()-metode. (Siden vi har iterator()-metoden så har vi markert at klassen implementerer Iterable, men det er ikke viktig.)


public class LeagueTable implements MatchListener, Iterable<LeagueTableRow>{

       private List<MatchResult> matchResults;
       private List<LeagueTableRow> tableRows;

       public LeagueTable(List<String> participantNames) {
              this.matchResults = new ArrayList<MatchResult>();
              this.tableRows = new ArrayList<LeagueTableRow>();
              for (String participant : participantNames) {
                      tableRows.add(new LeagueTableRow(participant));
              }
       }

       private LeagueTableRow findEntry(String participant) {
              for (LeagueTableRow entry : tableRows) {
                      if (entry.getParticipant().equals(participant)) {
                             return entry;
                      }
              }
              return null;
       }
       public Iterator<LeagueTableRow> iterator() {
              return tableRows.iterator();
       }

       public int getParticipantPoints(MatchResult match, String participant) {
              if (match.isWinner(participant)) {
                      return 3;
              } else if (match.isParticipant(participant) && match.isDraw()) {
                      return 1;
              }
              return 0;
       }
       private void addPoints(MatchResult matchResult, String partipant) {
              findEntry(partipant).addPoints(getParticipantPoints(matchResult, partipant));
       }

       public void addMatchResult(MatchResult matchResult) {
              if (findEntry(matchResult.getParticipant1()) == null || findEntry(matchResult.getParticipant2()) == null) {
                      throw new IllegalArgumentException("Both teams must be part of the league");              }
              matchResults.add(matchResult);
              addPoints(matchResult, matchResult.getParticipant1());
              addPoints(matchResult, matchResult.getParticipant2());
              Collections.sort(tableRows);
       }
}
Oppgave c)

  Tegn et objekt/instansdiagram (figur som viser instansene i en objektstruktur og deres innhold) som tilsvarer tabellen over, for strukturen av MatchResult-, LeagueTable- og evt. LeagueTableRow-objekter.

Denne deloppgaven er ment å hjelpe en å tenke over (logikken til) datastrukturen. Instansdiagram viser hvilke objekter som eksisterer, verdiene til feltene og koblinger dem imellom. En kan velge å tegne koblinger som piler med navn på eller med eksplisitte tabeller med piler. Vi er uansett ikke nøye på notasjonen (men det må være en figur som viser instanser og ikke klasser!).

 
Oppgave d)

I en ordentlig fotballtabell så spiller også totalt antall mål for og imot og resultat i innbyrdes kamper inn på tabellen. Beskriv kort med tekst og kode hvordan du vil støtte dette. 

Klassen for en tabellrad må utvides til å holde mer informasjon. Koden for compareTo må først sjekke tallene som har størst prioritet ift. sammenligningen, f.eks. beregne differansen og returnere denne dersom den er ulik 0. Så fortsetter en på samme måte med de andre tallene for sammenligning. Håndtering av innbyrdes oppgjør er vanskelig og krever at en slår opp i tabellens liste over kamper. Det kan gjøres på to måter, enten ved å inkludere en referanse til tabellen i tabellraden eller ved å la tabellklassen implementere Comparator, fordi en da kan slå opp innbyrdes oppgjør i tabellen. Det viste seg at sjekk av innbyrdes oppgjør ikke var kjent for så mange, så det ble ikke vektlagt.

 
Oppgave a)

 

 

Oppgave b)

 

 
Oppgave c)

 

 
Oppgave d)

 

 
Oppgave e)

 

 
Oppgave f)

 

 

 

 

  • No labels