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

Compare with Current View Page History

« Previous Version 4 Next »

TimeInterval

TimeInterval-klassen skal representere et del av en dag, f.eks. en forelesning eller møtetidsrom til bruk i en avtalekalender. Klassen inneholder to tidspunkt, begge representert med to heltall for timen og minuttet. Merk at det finnes flere måter å representere dette på, f.eks. representere tidspunktene som antall minutter siden midnatt (gir enklere logikk) eller intervallet med start-tidspunkt og lengde i minutter. Disse alternativene diskuterer vi ikke her.

Her er variablene deklarert i en klassedefinisjon:

public class TimeInterval {
	int startHour; // timen intervallet starter, med 0 som første time etter midnatt
	int startMin; // minuttet innen start-timen som intervallet starter på
	int endHour; // timen intervallet slutter
	int endMin; // minuttet innen slutt-timen, som intervallet slutter på
}

En liten detalj må avklares: Er slutt-tidspunktet med i intervallet eller ikke? Hvis vi f.eks. setter endHour til 16 og endMin til 0, betyr det at forelesningen er ferdig til kl. 16:00 eller at vi bruker det første minuttet av 16-timen? Vi velger den første tolkningen, så slutt-tidspunktet er først minutt som ikke er med i intervallet. Dette betyr noe for hva som er gyldig tilstand, som vi skal se.

Gyldig tilstand

Før vi begynner å kode klassen, kan det være lurt å tenke litt på hvilke verdier som er gyldige for hver av variablene. Det er opplagte begrensninger på hver av tallene: De som representerer timer må være >= 0 og < 24, mens de som representerer minutter (innen en time) må være >= 0 og < 60. Dette er regler vi må sørge for at overholdes, så det ikke blir kluss i beregningene.

For et tidsintervall er det også en regel som som gjelder verdiene samlet: Vi krever at et tidsintervall ikke kan ha negativ lengde, altså må endHour:endMin-tidspunktet være etter eller det samme som startHour:startMin-tidspunktet. Det gir jo ikke mening å ha forelesning fra 16:00 - 8:00. Nå er detaljen nevnt over viktig: Slik vi har tolket slutt-tidspunktet, så kan det altså falle sammen med start-tidspunktet og gi et tidsintervall med lengde 0.

Innkapsling

Innkapsling er teknikken for å sikre tilstand og består av 1) synlighetsmodifikatorer og 2) innkapslingsmetoder.

Synlighetsmodifikatorer

Synlighetsmodifikatorer begrenser hvilke variabler (felt) og metoder som andre klasser får bruke. Den generelle regelen for å sikre gyldig tilstand er at alle felt skal være private og dermed utilgjengelig for andre klasser, mens innkapslingsmetodene skal være offentlige (public). Grunnen er jo at offentlige felt ikke kan beskyttes med kode som validerer verdier. Vi kan forøvrig også ha private metoder, f.eks. hjelpemetoder, som ikke er garantert å sikre gyldig tilstand.

Innkapslingsmetoder

Innkapslingen håndteres av et sett med metoder som gir mulighet til å sette og endre tilstanden (feltene), men som samtidig sikrer at ingen enkelt-variabler eller objektet som helhet får ugyldig tilstand (iht. reglene definert over) ved at tilstanden valideres før den settes/endres. Det er viktig å huske at også start-tilstanden må være gyldig, så konstruktørene er også en del av innkapslingen.

Det finnes ingen klare regler for hvilke metoder en klasse må ha, men det er ikke uvanlig å (vurdere å) ha såkalte gettere og settere for hvert felt. For TimeInterval-klassen betyr det å muligheten til å sette hver variablen for seg, altså int getStartHour() og void setStartHour(int hour) for å lese og sette startHour, int getStartMin() og void setStartMin(int min) for å lese og sette startMin osv. for endHour og endMin. Koden for et get/set-par (uten validering) blir som vist under til venstre:

public int getStartHour() {
	return startHour;
}
    
public void setStartHour(int startHour) {
    this.startHour = startHour;
}
public void setStart(int startHour, int startMin) {
    this.startHour = startHour;
    this.startMin = startMin;
}

Nå kan man spørre seg om det ikke er mest praktisk å kunne sette mer enn én verdi om gangen, f.eks. sette start-tidspunktet, dvs. startHour og startMin, med én metode og tilsvarende for slutt-tidspunktet. Denne varianten er vist over til høyre. I praksis spiller det ikke så mye rolle for hvordan vi håndterer validering, som vi nå skal se på.

Validering

Hensikten med å lage setter-metoder er ikke først og fremst å gjøre det praktisk å sette tilstand, men å kunne sikre gyldig tilstand ved å legge til valideringskode som er garantert å alltid blir utført. Den enkleste varianten er å legge tilordningen inni en if, og ha en betingelse som sikrer at tilordningen kun blir utført om den nye verdien er gyldig, som vist til venstre under. En annen variant er å ha en if med den motsatte betingelsen, altså om den nye verdien er ugyldig, og utløse et passende unntak, som vist under til høyre.

public void setStartHour(int startHour) {
	if (startHour >= 0 && startHour < 24) {
		this.startHour = startHour;
	}
}
public void setStartHour(int startHour) {
	if (startHour < 0 || startHour >= 24) {
		throw new IllegalArgumentException("An hour must be >= 0 and < 24, but was " + startHour);
	}
	this.startHour = startHour;
}

Forskjellen mellom disse virker ikke så stor, men det er nokså vesentlig å vite om ugyldig argument gir unntak som kræsjer med programmet, eller ikke! Varianten til høyre er generelt å anbefale, fordi å stiltiende ignorere ugyldige argumenter vil bare utsette problemet til siden og gjøre kilden til problemet vanskeligere å finne.

Siden vi i TimeInterval-klassen har to sett med like variabler, så vil det være en fordel å flytte valideringskoden over i egne metoder, som kalles fra setter-metodene. En har typisk to typer metoder, de som returnerer en boolean verdi som angir om verdier er gyldige og de som både sjekker gyldighet og utløser unntak hvis så ikke er tilfelle. Disse variantene er vist under hhv. til venstre og høyre. Merk at teknikken er den samme om en har setter-metoder for flere verdier på en gang, som setStart-metoden over. En har likevel valideringsmetoder for hver enkeltverdi og kaller dem begge.

private boolean isValidHour(int hour) {
	return hour >= 0 && hour < 24;
}

public void setStartHour(int startHour) {
	if (isValidHour(startHour)) {
		this.startHour = startHour;
	}
}
private void checkHour(int hour) {
	if (hour < 0 || hour >= 24) {
		throw new IllegalArgumentException("An hour must be >= 0 and < 24, but was " + hour);
	}
}

public void setStartHour(int startHour) {
	checkHour(startHour);
	this.startHour = startHour;
}

Uansett hvilken variant en velger, så kan det være greit å ha en public boolean isValid...-metode, slik at andre klasser selv kan sjekke om verdier er gyldige før de evt. kaller metoden. Tenk f.eks. at en bruker fyller inn et skjema for et slik tidsinterval med et innfyllingsfelt for hver verdi. Med en offentlig valideringsmetode kan en sjekke gyldighet og markere innfyllingsfelt som ugyldige, uten å først kalle setter-metoden og i etterkant sjekke om det gikk.

Validering av gyldighet på tvers av felt

Koden over sjekke ikke om TimeInterval-objektet er gyldig som helhet, altså om slutt-tidspunktet er det samme som eller etter start-tidspunktet. Teknikken er generelt den samme: En lager egne metoder for hver regel. Forskjellen er at en må kalle den fra alle setter-metodene, siden alle potensielt kan bryte regelen for gyldig tilstand. Koden er vist under, basert på varianten med check...-metoder. Vi har her tatt med alle setter-metodene, for å nettopp vise at alle disse må forholde seg til regler for både enkeltverdier og totaltilstanden.

private void checkHour(int hour) {
	if (hour < 0 || hour >= 24) {
		throw new IllegalArgumentException("An hour must be >= 0 and < 24, but was " + hour);
	}
}

private checkMin(int min) {
	if (min < 0 || min >= 60) {
		throw new IllegalArgumentException("A minute must be >= 0 and < 60, but was " + hour);
	}
}

private checkStartLessThanOrEqualToEnd(int startHour, int startMin, int endHour, int endMin) {
	if (endHour < startHour || (endHour == startHour && endMin < startMin)) {
		throw new IllegalArgumentException("End cannot be less than start");
	}
}

public void setStartHour(int startHour) {
	checkHour(startHour);
	checkStartLessThanOrEqualToEnd(startHour, this.startMin, this.endHour, this.endMin);
	this.startHour = startHour;
}

public void setStartMin(int startMin) {
	checkMin(startMin);
	checkStartLessThanOrEqualToEnd(this.startHour, startMin, this.endHour, this.endMin);
	this.startMin = startMin;
}

public void setEndHour(int endHour) {
	checkHour(endHour);
	checkStartLessThanOrEqualToEnd(this.startHour, this.startMin, endHour, this.endMin);
	this.endHour = endHour;
}

public void setEndMin(int endMin) {
	checkMin(endMin);
	checkStartLessThanOrEqualToEnd(this.startHour, this.startMin, this.endHour, endMin);
	this.endMin = endMin;
}

Det er viktig å merke seg hvilke verdier som gis som argument til checkStartLessThanOrEqualToEnd-metoden. I hver setter-metode gis den nye verdien som ønskes satt sammen med de andre og eksisterende felt-verdiene. Dette gjøres ekstra tydelig ved bruk av this foran feltene, selv om det det strengt tatt ikke er nødvendig.

 

  • No labels