Versions Compared

Key

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

Denne siden er under arbeid.

Kodeskjelettet under er det samme for alle deloppgavene. Det er markert hvilke metoder og klasser som skal implementeres i hvilke deloppgaver. De kan også endres i andre deloppgaver basert på dine egne kodevalg.

KIldekoden som ble brukt under utvikling av oppgavene finner du på GitHub.

Temaet for oppgaven er administrasjon av sykkelutleie. Sykkelutleietjenesten (BikeRental) tilbyr leie av sykler (Bike) som plukkes opp og settes tilbake på utvalgte stasjoner spredt rundt omkring. (Senere oppgaver legger opp til at en kan leie ut andre fremkomstmidler også). Syklene har GPS, slik at de hele tiden spores (GeoLocation). Den som skal leie sykkel (Person) bruker en app for å få oversikt over hvor (ved hvilke stasjoner) det er tilgjengelige sykler. Deretter er det bare å identifisere sykkelen, angi hvor lenge man ønsker å leie den, og en kan sykle av gårde. Når en er ferdig med å bruke sykkelen, må den settes tilbake på en stasjon (ikke nødvendigvis den samme som en tok den fra). Ved hjelp av appen angir man at leieforholdet er avsluttet. Leieprisen blir da beregnet og pengene trukket.
 
Prisen er basert på påbegynte timer, og merk at en ikke betaler for mer enn faktisk bruk. Hvis man altså angir at en ønsker å leie en sykkel i tre timer, men leverer den tilbake etter en halv time, så betaler en for én times bruk. Det er mulig å utvide leietiden underveis, noe som utløser et lite gebyr. Hvis en utvider leietiden eller leverer sykkelen etter at sykkelen skulle vært levert, så påløper det også et gebyr.
 
Del 1 av oppgaven fokuserer på administrasjon av leieforholdet. I første omgang beregnes pris kun fra tidsbruk, mens i del 2 utvides det med gebyrer og kvittering. Del 3 fokuserer på å gjøre det lettere å bytte måten prisen beregnes. Del 4 handler om mobil-appen og del 5 om testing.

Expand
titleKodeskjelett


Code Block
languagejava
titleKodeskjelett
collapsetrue
public class Person {

    /**
     * Withdraws the provided amount from this person's account.
     * If the transaction couldn't be completed, i.e. no money was actually withdrawn,

Gammel eksamen lagt inn som mal. Mer kommer!

Expand
titleDel 1 - Dice og DiceIterator
/** * Represents a set of die values. */ public class Dice implements Iterable<Integer> { /** * Counters for each possible die value. * The counter at index i (0-5) is the counter for the die value i+1 (1-6). * I.e. the value at index 2 is the counter for die value 3. */ private final int[] valueCounters; /**

     * 
Initializes
one 
this
of 
Dice
the 
with
indicated 
the
exceptions 
values
will 
in
be 
dieValues
thrown.
     * @param
dieValues sequence of die values, not counter values
 amount the amount to withdraw
     * @throws IllegalArgumentException
     *
/
 @throws IllegalStateException
    
public Dice(Iterator<Integer> dieValues)
 */
    public void withdraw(double amount) throws IllegalArgumentException, IllegalStateException {
        
???
// ...
    }
}

public class GeoLocation 
/**
{

    private final 
*
double 
Initializes
latitude;
 
this
 
Dice
 
with
 
the
private 
values
final 
in dieValues.
double longitude;

    public 
* @param dieValues sequence of die values, not counter values */
GeoLocation(double latitude, double longitude) {
        this.latitude = latitude;
       
public Dice(Iterable<Integer> dieValues) {
 this.longitude = longitude;
    }

    
???
public double getLatitude() {
    
}
    return latitude;
    
@Override
}

    public 
Iterator<Integer>
double 
iterator
getLongitude() {
        return 
new DiceIterator(this)
longitude;
    }

    /**
     * 
@return
Computes 
the
distance 
number
to 
of
other 
die values
Location
     *
/
 @param other
    
public
 
int
* 
getDieCount() {
@return distance to other Location
     
??? }
*/
    public double distance(GeoLocation other) {
    
/**
    
* Die values are considered ordered, with the smallest die values
distance(this.latitude,this.longitude,other.)
        // ??? 1 b)
    }

 
*
 
at
 
the
 
lowest index. The value at a specific index
/** calculates the distance between two points,
     * 
must
 
be
 
computed from
given the 
counters
latitude/longitude 
in valueCounters.
of those points).           
*
 
@param
 
dieNum
     
*
 
@return
 
the
 
value
 
of
 
die
 
number
 
dieNum
     
*
 
@throws
 
an
 
appropriate
 
exception,
 
if

 
dieNum
 
is
 
out
 
of
 
range
*
     */
    public static 
int
double 
getDieValue
distance(
int
double 
dieNum) { ???
lat1, double lon1, double lat2, double lon2) {
      
}
 
 // return null 
/**
er bare tull, den har 
*
egentlig 
@param
beregning 
value
her.
     
*
 
@return the number of dice with the provided value
  return null; // Denne kompliserte beregningen er utelatt. 
     
*/ public int getValueCount(int value) {
   // Anta at den er implementert.
    }
}

public class Bike {

    // ??? 1 c)

}

public class BikeRental 
}
{

    /
** * @param dice
/ ??? 1 d)

    /**
     * 
@return
Counts 
true
the 
if
number 
all
of 
die
available 
values
bikes 
in
within 
the
a 
Dice
certain 
argument
distance 
appear
of 
in
a 
this
provided 
Dice
location.
     *
/
 @param location
    
public
 
boolean
* 
contains(Dice dice) {
@param distance
     * @return the 
??? } /**
number of available bikes within a certain distance of a provided location
     */
 
@param
 
dice
  private int countAvailableBikesNearby(GeoLocation 
*
location, 
@return
double 
true
distance) 
if
{
 
this
 
Dice
 
and
 
the
 
one
 
provided
 
have
 
exactly the same die values
// ??? 1 e) 
    }

    /*
/
*
    
public
 
boolean
* 
isSame(Dice dice) { ??? }
Finds the closest station (location) within the provided (maximum) distance of the provided bike
     
/
*
*
 @param bike
     * @param 
dice a Dice object
minDistance
     * @return 
a
the 
new
closest 
Dice
station 
instance
(location) 
with
within the 
all
provided 
the
(maximum) 
die values in * this Dice and
distance of the 
Dice
provided 
argument combined
bike
     */
    
public
private 
Dice
GeoLocation 
add(Dice dice
getStationNearby(Bike bike, double maxDistance) {
        // ??? 1 e)
    }

    /**
     * 
@param
@return 
dice
the bikes that currently are 
*
rented
 
@return
 
a
 
new
 
Dice instance with the die values from this Dice, but
 */
    private Collection<Bike> getRentedBikes() {
     
*
 
without
 
those
 
from the Dice argument
// ??? 1 e)
    }

    /**
     * @return the bikes that are close to a station (within 30m), but still are rented
     */
    
public
private 
Dice
Collection<Bike> 
remove
getUnreturnedBikes(
Dice dice
) {
        // ??? 1 e)
    }
}
Expand
titleInnledning til del 1

Denne delen av oppgaven omhandler Dice-klassen, som brukes til å representere (verdien av) én eller flere terninger (engelsk: dice = terninger, die = terning). En slik klasse kan være nyttig i mange typer terningspill, for å representere terninger som nettopp er slått eller de av et sett terninger som en får poeng for.

Merk at poengene ikkeer en del av Dice-klassen, de håndteres av andre klasser som beskrives senere.

Dice-klassen er vist under, ??? erstatter kodefragmenter som det spørres om i oppgaveteksten., mens ... står for kode som er utelatt fordi det ikke er viktig. Vi kreverher at terningverdiene skal representeres på en bestemt måte, som et sett tellere for hver mulig terningverdi, og har derfor oppgitt deklarasjonen av valueCounters. Det skal ikkevære nødvendig å deklarere andre felt enn dette.

Eksempel: Hvis et Dice-objekt representerer de fire verdiene 1, 2, 2 og 4, så vil tellerverdiene i valueCountersvære 1, 2, 0, 1, 0 og 0, altså 1 ener, 2 toere, ingen treere, 1 firer, ingen femmere og ingen seksere. For getDieValue-metoden sin del, vil eneren være på indeks 0, de 2 toerne vil være på indeks 1 og 2, og fireren vil være på indeks 3.

Code Block
Oppgave a) - Konstruktører (8 poeng)

Skriv kode for de to konstruktørene (merk forskjellen i argumenttype), slik at valueCounters blir riktig initialisert. Du SKAL IKKE innføre andre felt. Du kan lage hjelpemetoder om du ønsker. For begge konstruktørene er argumentet en sekvens med terningverdier (altså ikke tellere) som den nyinitialiserte instansen skal representere.

Merk at det er lov å lage ekstra konstruktører for å gjøre andre metoder, f.eks. add og remove, enklere å skrive.

Expand
titleLF

Her er poenget å skjønne hvordan tabeller virker (opprettes, leses fra og skrives til) og logikken bak valueCounters-feltet, samt hvordan iterere med Iterator (hasNext() og next()) og Iterable (iterator()).

Code Block
public Dice(Iterator<Integer> dieValues) {
	this.valueCounters = new int[6];
	while (dieValues.hasNext()) {
		int dieValue = dieValues.next();
		if (! (dieValue >= 1 && dieValue <= 6)) {
			throw new IllegalArgumentException("A die value must be in the range 1-6");
		}
		// add one to the corresponding counter
		this.valueCounters[dieValue - 1]++;
	}
}

public Dice(Iterable<Integer> dieValues) {
	this(dieValues.iterator());
}

Vanlige feil: En del skrev kode som om de skulle lagre enkeltverdiene, ikke tellerne for hver mulig verdi. Da blir løsningen som på den ordinære eksamen, og det gir ikke uttelling.

Oppgave b) - Terningverdier (8 poeng)

Skriv metodene getDieCountgetDieValue og getValueCount iht. forklaringen og koden gitt tidligere (API-beskrivelsen).

Expand
titleLF

Når en først har tellerne i valueCounters, så er getDieCount- og getValueCount-metodene trivielle. getDieValue er litt mer kinkig, da en må beregne hvilket tall som vil falle på en bestemt indeks, uten noen liste over alle terningverdiene.

Code Block
public int getDieCount() {
	int count = 0;
	for (int counter : valueCounters) {
		count += counter;
	}
	return count;
}

public int getDieValue(int dieNum) {
	if (dieNum < 0 || dieNum >= getDieCount()) {
		throw new IllegalArgumentException(dieNum + " is out of range");
	}
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		int counter = valueCounters[dieValue - 1];
		if (dieNum < counter) {
			return dieValue;
		}
		dieNum -= counter;
	}
	// should never come here
	throw new IllegalArgumentException();
}

public int getValueCount(final int value) {
	return valueCounters[value - 1];
}


Vanlige feil: Det var en del som ikke skjønte hva metodene skulle gjøre, selv om det var de samme metodene som på ordinær eksamen. De må imidlertid skrives annerledes, siden terningene er representert på en annen måte.

 

Oppgave c) - Flere metoder (8 poeng)

Skriv metodene containsisSameadd og remove, som alle tar et Dice-objekt som eneste argument. Merk at ingen av disse endrer på verken this-objektet eller argumentet.

Her er noen eksempler på bruken av disse metodene, hvor [t1, t2, t3, ... tn] brukes som pseudo-synaks for et Dice-objekt som representerer terningverdiene t1-tn:

[1, 2, 2, 4].contains([1, 4]) returnerer true, mens

[1, 2, 4].contains([2, 2]) returnerer false.

[1, 2, 2, 4].isSame([1, 2, 2, 4]) returnerer true, mens

[1, 2, 2, 4].isSame([1, 2, 4]) returnerer false

[1, 2].add([1, 4]) returnerer [1, 1, 2, 4]

[1, 1, 2].remove([1, 4]) returnerer [1, 2]

Merk at removei kke har samme logikk som Collectionsin removeAll-metode.

Expand
titleLF

Disse metodene kan forsåvidt skrives kun vha. getDieValue-metoden og konstruktørene (som de fleste gjorde på den ordinære eksamenen), men det er mye enklere å utnytte at vi representerer terningene med tellere. Her utnytter vi også at vi kan lage tomme Dice-instanser, og så etterpå justere tellerne i valueCounters.

Code Block
// Et Dice-objekt inneholder et annet hvis det er minst like mange av hver terning-verdi
public boolean contains(final Dice dice) {
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		if (this.valueCounters[dieValue - 1] < dice.getValueCount(dieValue)) {
			return false;
		}
	}
	return true;
}

// To Dice-objekter er like hvis det ene inneholder det andre og omvendt!
// Evt. kan kan sjekke at alle valueCounters-verdiene er like
public boolean isSame(final Dice dice) {
	return contains(dice) && dice.contains(this);
}

// To Dice-objekter kan slås sammen ved å legge sammen tellerne
public Dice add(final Dice dice) {
	// Lager et tomt Dice-objekt og justerer tellerne i løkka
	Dice result = new Dice(Arrays.asList());
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		result.valueCounters[dieValue - 1] = this.valueCounters[dieValue - 1] + dice.valueCounters[dieValue - 1];
	}
	return result;
}

// En kan trekke et Dice-objekt fra et annet ved å trekke den enes tellere fra den andres.
// En må huske å unngå negative tellere.
public Dice remove(final Dice dice) {
	// Lager et tomt Dice-objekt og justerer tellerne i løkka
	Dice result = new Dice(Arrays.asList());
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		result.valueCounters[dieValue - 1] = Math.max(0, this.valueCounters[dieValue - 1] - dice.getValueCount(dieValue));
	}
	return result;
}

Vanlige feil:

 

Oppgave d) - DiceIterator (6 poeng)

Lag en implementasjon av Iterator<Integer>kalt DiceIterator, slik at den kan brukes slik det er vist i Dice sin iterator()-metode. Du kan anta at valueCounters-feltet i Dice er synlig i DiceIterator-klassen.



    /**
     * Called when a person starts renting a bike by taking it from a station.
     * Checks the arguments before registering all necessary info of the rental.
     * @param person
     * @param now the start time of the rental
     * @param returnTime the expected return time
     * @throws (some subclass of) RuntimeException if the now isn't before returnTime
     * @throws (some subclass of) RuntimeException if the bike isn't available for rental
     */
    public void rentBike(Person person, Bike bike, LocalDateTime now, LocalDateTime returnTime) {
        // ??? 1 f)
    }

    /**
     * Called when a person extends an ongoing bike rental.
     * Checks the arguments before registering all necessary info of the rental extension.
     * @param person
     * @param bike
     * @param now the time the extension starts
     * @param returnTime the (new) expected return time
     * @throws (some subclass of) RuntimeException if the now isn't before returnTime
     * @throws (some subclass of) RuntimeException if the bike isn't currently being rented
     * @throws (some subclass of) RuntimeException if person isn't currently renting the bike
     */
    public void extendRental(Person person, Bike bike, LocalDateTime now, LocalDateTime returnTime) {
        // ??? 2 b)
    }

    /**
     * Called when a person returns a bike.
     * Checks the arguments, computes the price, performs the payment and clears the rental info.
     * Note that if the payment isn't completed, the rental info should not be cleared.
     * @param person
     * @param bike
     * @param now the time the bike is returned
     * @throws (some subclass of) RuntimeException if the bike isn't currently being rented by the person argument
     * @throws (some subclass of) RuntimeException if person isn't near (within 30m of) a station
     */
    public void returnBike(Person person, Bike bike, LocalDateTime now) {
        // ??? 1 f)
    }


    /**
     * Called when the Person has returned a Bike.
     * Displays initial rent period
     * Displays extensions of the rent period
     * Computes and displays total cost, including fees for late return.
     * @param person
     * @param bike
     */
    void printReceipt(Person person, Bike bike) {


    }
}

// Kontroller-logikk

    // refers to the app's BikeRental object
    private final BikeRental bikeRental;

    // refers to the app user
    private final Person me;

    // refers to currently selected bike in map
    private Bike bike;

??? 4 a, b)

/**
 * @return a LocalDataTime object corresponding to the from input field value
 */
private LocalDateTime getFromTime() {
        ??? 4 b)
        }
/**
 * Updates the from input field value according to the LocalDateTime argument
 * @param time
 */
private void setFromTime(final LocalDateTime time) {
        ??? 4 b)
        }

/**
 * @return a LocalDataTime object corresponding to the from input field value
 */
private LocalDateTime getToTime() {
        ??? 4 b)
        }
/**
 * Updates the from input field value according to the LocalDateTime argument
 * @param time
 */
private void setToTime(final LocalDateTime time) {
        ??? 4 b)
        }

// Husk kvittering.
        Kvitteringsformat

        Anta følgende sekvens av hendelser: / Presume the following sequence of events:
        1 - En person leier en sykkel kl. 12:10 og angir at den skal returneres kl. 14:20.
        1 - A persons rents a bike at 12:10 and specifies the return time to 14:20.
        2 - Kl. 14:30 utvides leietiden til 15:10 (Merk forsinkelsen)
        2 - At 14:30 the rent period is extended to 15:10. Mind the delay in extension.
        3 - Kl. 15:15 leveres sykkelen
        3 - 15:15: The bike is delivered.
        Dette gir totalt 4 påbegynte timer = 4 * 10 kr,
        1 utvidelse = 5 kr og 2 forsentkomminger = 2 * 10 kr.

        Kvitteringen skal da se ut som følger: The receipt should look as follows:

        Initial rental from 12:10 to 14:20
        Extension 1 from 14:30 to 15:10
        Bike returned at 15:15
        Total cost: 65 kr,-



Expand
titleDel 1 – BikeRental-, Bike-, og Location-klassene (35%)

BikeRental-klassen administrerer stasjonene, syklene og utleieforhold. Vi gjør det enkelt og identifiserer stasjonene med et geografisk punkt (GeoLocation, se skjelettet), altså lengde- og breddegrad. GeoLocation brukes også for lagring av posisjonen til syklene.

Oppgave 1 a) - GeoLocation

GeoLocation er kodet slik at instansene ikke kan endres. Hva er fordelen med dette?

Expand
titleLF

Objekter som ikke kan endres, kan deles/brukes i flere datastrukturer uten fare for kluss. Hvis delte objekter kan endres, så må en være mer nøye på hva en gjør.

Oppgave 1b) - GeoLocation

Skriv ferdig double distance(GeoLocation)-metoden i GeoLocation.

Expand
titleLF

Her var poenget å kalle den eksisterende statiske distance-metoden som tar to koordinatpar.

Vi legger også ved en implementasjon av denne hjelpemetoden, mens den i skjelettet var tom.

Code Block
public class GeoLocation {

// Slettet gettere og settere etc.

// Kode for 1b
 public double distance(final GeoLocation other) {
  return distance(latitude, longitude, other.latitude, other.longitude);
 }


// Under følger kode for metoden dere kunne forvente at eksisterte.
 /*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 /*::                                                                         :*/
 /*::  This routine calculates the distance between two points (given the     :*/
 /*::  latitude/longitude of those points). It is being used to calculate     :*/
 /*::  the distance between two locations using GeoDataSource (TM) prodducts  :*/
 /*::                                                                         :*/
 /*::  Definitions:                                                           :*/
 /*::    South latitudes are negative, east longitudes are positive           :*/
 /*::                                                                         :*/
 /*::  Passed to function:                                                    :*/
 /*::    lat1, lon1 = Latitude and Longitude of point 1 (in decimal degrees)  :*/
 /*::    lat2, lon2 = Latitude and Longitude of point 2 (in decimal degrees)  :*/
 /*::  Worldwide cities and other features databases with latitude longitude  :*/
 /*::  are available at http://www.geodatasource.com                          :*/
 /*::                                                                         :*/
 /*::  For enquiries, please contact sales@geodatasource.com                  :*/
 /*::                                                                         :*/
 /*::  Official Web site: http://www.geodatasource.com                        :*/
 /*::                                                                         :*/
 /*::           GeoDataSource.com (C) All Rights Reserved 2015                :*/
 /*::                                                                         :*/
 /*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 public static double distance(final double lat1, final double lon1, final double lat2, final double lon2) {
  final double theta = lon1 - lon2;
  double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
  dist = Math.acos(dist);
  // convert to degrees
  dist = rad2deg(dist);
  dist = dist * 60 * 1.1515;
  // convert to meters
  dist = dist * 1609.344;
  return dist;
 }
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 /*:: This function converts decimal degrees to radians       :*/
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 private static double deg2rad(final double deg) {
  return (deg * Math.PI / 180.0);
 }
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 /*:: This function converts radians to decimal degrees       :*/
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 private static double rad2deg(final double rad) {
  return (rad * 180 / Math.PI);
 }
}

Vanlige feil:

 Det viser seg ganske vanlig at folk på eksamen ikke leser hele oppgaven, ei heller hele klassen til en metode som skal implementeres, før en implementerer den. Dette er ikke lurt.

Oppgave 1c) - Bike

Bike-klassen skal ha assosiasjoner (koblinger) til Person- (se skjelettet) og GeoLocation-klassene iht. følgende klassediagram.


PlantUML Macro
class Bike
class Location
class Person
Bike --> Location: location
Bike --> Person: renter

(Location skulle hett GeoLocation, men det var tydelig nok i forhold til teksten hva som er ment.)

Begge disse assosiasjonene er dynamiske, dvs. både location og renter (låntaker) kan endres. location-koblingen endres (automagisk, du kan bare forholde deg til at location hele tiden oppdateres) når sykkelen beveger seg, mens renter-koblingen endres når leieforhold innledes og avsluttes.

Skriv felt og metoder for location- og renter-assosiasjonene iht. Java sine kodingskonvensjoner.

Expand
titleLF

Bike skal potensielt lagre én lokasjon (GeoLocation) og én Person. Her spør vi kun om å implementere feltene, samt metoden for å sette inn og hente ut verdiene. Lager gettere og settere iht. Java-konvensjonene.

Code Block
public class Bike {

   private GeoLocation location;
   private Person renter;

   public GeoLocation getLocation() {
      return location;
   }

   public void setLocation(final GeoLocation location) {
     this.location = location;
   }

   public Person getRenter() {
      return renter;
   }

   public void setRenter(final Person renter) {
      this.renter = renter;
   }

Vanlige feil:

Oppgave 1d) - BikeRental

BikeRental må holde oversikt over alle stasjonene, representert med GeoLocation, og alle syklene (Bike). Skriv nødvendige variabel-deklarasjoner (felt) for dette og forklar hva som styrer valget av typer. Du skal ikke skrive metoder for å konfigurere systemet med stasjoner og sykler, men anta at slike finnes.

Expand
titleLF

Her skulle du velge hva slags representasjon du vil ha for alle stasjonene og syklene i systemet. En trenger en klasse som kan holde mange elementer, og spørsmålet koker gjerne ned til hvilken Collection-subklasse en skal bruke i deklarasjonen. Tommelfingerregelen er å velge det enkleste grensesnittet, som samtidig er godt nok. Vi har valgt å bruke en Collection for begge to, siden vi ikke trenger metoder fra List-grensesnittet (som utvider Collection med indeksbaserte metoder).

Du skulle ikke (her) skrive de ulike metodene som manipulerte systemet mhp fordeling av sykler og stasjoner.

Code Block
public class BikeRental {

	private final Collection<GeoLocation> places = new ArrayList<GeoLocation>();
    private final Collection<Bike> allBikes = new ArrayList<Bike>();

	// metoder vi senere antar finnes:

	void addPlace(final GeoLocation location) {
		places.add(location);
	}

	void addBike(final Bike bike, final GeoLocation location) {
		if (location != null) {
			bike.setLocation(location);
		}
		allBikes.add(bike);
	}

Vanlige feil:

Beskrivelse av oppgavene 1e og 1f

I første omgang skal du skrive forenklet kode for leie og retur av sykler, hvor det ikke tas hensyn til annet enn start- og slutt-tidspunkt for leieforholdet. Forlenging av leie ser vi foreløpig bort fra. renter-assosiasjonen i Bike skal holde rede på hvem (Person) som evt. leier en sykkel (Bike). Du bestemmer selv hvor/hvordan du vil representere når leieforholdet starter og når det forventes at sykkelen leveres tilbake. Det viktigste er at informasjonen kan brukes når sykkelen leveres og leien skal beregnes og betales.

Oppgave 1e) - BikeRental

Skriv metodene under i klassen BikeRental (se skjelettet til venstre for detaljene). Disse metodene bygger på representasjonen du valgte i 1d.

  • countAvailableBikesNearby - teller hvor mange ledige sykler det er innen en viss avstand (hjelper en potensiell låner (renter) å finne ut om det er sykler i nærheten)
  • getStationNearby – finner (lokasjonen til) stasjonen som er nærmest en sykkel og innenfor en viss minimumsavstand (hjelper en låner (renter) å finne ut hvor en sykkel kan settes tilbake)
  • getRentedBikes – returnerer alle syklene som er lånt ut
  • getUnreturnedBikes – returnerer alle syklene som er plassert ved en stasjon, men som fortsatt er knyttet til en låner (renter)  (brukes når en skal sende varsler til de som har satt fra seg sykkelen, men glemt å angi den som levert)
Expand
titleLF

Disse oppgavene dreide seg stort sett om filtrering av de ulike strukturene for stasjoner og sykler, og metodekall for å finne distanse osv. Det var oppgaver som kunne løses veldig bra ved hjelp av Streams og lambdaer, vi viser et par eksempler på slike.

Code Block

    private int countAvailableBikesNearby(final GeoLocation location, final double distance) {
        int count = 0;
        for (final Bike bike : allBikes) {
            if (bike.getRenter() == null && bike.getLocation().distance(location) <= distance) {
                count++;
            }
        }
        return count;
        //        return allBikes.stream().filter(bike -> bike.getRenter() == null && bike.getLocation().distance(location) <= distance).count();
    }
    private GeoLocation getStationNearby(final Bike bike, final double maxDistance) {
        GeoLocation closestPlace = null;
        double minDistance = maxDistance;
        for (final GeoLocation place : places) {
            final double distance = bike.getLocation().distance(place);
            if (distance < minDistance) {
                closestPlace = place;
                minDistance = distance;
            }
        }
        return closestPlace;
    }

    private Collection<Bike> getRentedBikes() {
        final Collection<Bike> result = new ArrayList<Bike>();
        for (final Bike bike : allBikes) {
            if (bike.getRenter() != null) {
                result.add(bike);
            }
        }
        return result;
        //    return allBikes.stream().filter(bike -> bike.getRenter() != null).collect(Collectors.toList());
    }

    //    Kan dere skrive den siste uten Streams? 😊
    private Collection<Bike> getUnreturnedBikes() {
        return getRentedBikes().stream().filter(bike -> getStationNearby(bike, 30.0) != null).collect(Collectors.toList());
    }

Vanlige feil:

Oppgave 1f) - BikeRental (mer)

Skriv metodene rentBike og returnBike i klassen BikeRental (se skjelettet for detaljene, merk spesielt krav om utløsing av unntak). Disse metodene endrer tilstanden som brukes av de foregående metodene. Velg selv passende unntakstype(r).

LocalDateTime brukes for å representere tid (erstatter Date i moderne Java). Gjør nødvendige antakelser om metoder den har, som trengs for tidsberegninger, og bruk disse antakelsene.


rentBike – kalles når en person starter å leie en gitt sykkel
returnBike – kalles når en person leverer tilbake sykkelen og betaler for leien (i denne oppgaven skal pris være 10 kr. pr. påbegynte time)

Expand
titleDel 1 – BikeRental-, Bike-, og Location-klassene (35%)

Her var det viktig å lese javadokumentasjonen for metodene, og se nøye på hva unntakene skulle sjekke for samt utløse. I løsningsforslaget har vi laget en hjelpemetode checkNowIsBeforeReturnTime somsjekker to LocalDateTime mot hverandre ved hjelp av LDT.compareTo(LDT). Når en Person leier en sykkel må vi huske på å oppdatere dette i sykkelen.

Expand
titleLF

    public void rentBike(final Person person, final Bike bike, final LocalDateTime now, final LocalDateTime returnTime) {

        checkNowIsBeforeReturnTime(now, returnTime);

        if (bike.getRenter() != null) {

            throw new IllegalArgumentException(bike + " is currently being rented");

        }

        bike.setRenter(person);

    }


    private void checkNowIsBeforeReturnTime(final LocalDateTime now, final LocalDateTime returnTime) {

        if (now.compareTo(returnTime) >= 0) {

            throw new IllegalArgumentException("The start time, " + now + " is the same as or after the return time, " + returnTime);

        }

    }

    public void returnBike(final Person person, final Bike bike, final LocalDateTime now) {

        if (bike.getRenter() != person) {

            throw new IllegalArgumentException(bike + " isn't currently being rented by " + person);

        }

        final GeoLocation place = getStationNearby(bike, 30.0);

        if (place == null) {

            throw new IllegalArgumentException(bike + " isn't near (enough) a bike place");

        }

// part 2        person.withdraw(computePrice(person, bike, now)); // We didn't focus on the implementation of withdrawing money.

// part 2        this.printReceipt(person, bike, now);

        bike.setRenter(null);

// part 2        bike.clearRentalInfos();

    }



 




Expand
titleDel 2 – Gebyr og kvittering (20%)

BikeRental-klassen skal utvides til å støtte forlengelse av leie, beregning av gebyrer for forlengelse og for å forlenge eller levere tilbake sykkelen etter angitt tid. I tillegg skal det lagres nok data om dette, så en kan få skrevet ut en kvittering med forklaring på hvordan prisen framkom.


Oppgave 2a)

Forklar med tekst og/eller kode hvordan du vil lagre informasjon om evt. forlengelser av leieforholdet, slik at systemet har nok informasjon til å kunne

Expand
titleLF

Hver enkelt sykkel kan leies i flere perioder (start pluss utvidelser), men det må være av den samme personen. Informasjonen om alle disse periodene må lagres en plass. Vi må lagre start- og sluttidspunkt for alle disse periodene. Hvis en person utvider leietiden fem minutter etter at den forrige perioden var gått ut skal det legges på en avgift.

 

All denne informasjonen kan lagres på ulike steder. I LF har vi tatt utgangpunkt i at alle disse tidsperiodene er knyttet til én enkelt sykkel. En sykkel har allerede knyttet én person til seg, den som leier. Det er derfor naturlig å la sykkelobjektet lagre data om alle leieperiodene for denne personen. Dataene fjernes når en ny leieavtale inngås.

Vi introduserer en ny klasse, RentalInfo. Denne inneholder to LocalDateTime, en for starten av en periode og en for slutten. Bike har en liste av slike, og passende gettere/settere.

Code Block
languagejava
linenumberstrue
public class RentalInfo {

	private final LocalDateTime startTime;
	private final LocalDateTime endTime;

	public RentalInfo(final LocalDateTime startTime, final LocalDateTime endTime) {
  		this.startTime = startTime;
		this.endTime = endTime;
	}

	public LocalDateTime getStartTime() {
		return startTime;
	}

	public LocalDateTime getEndTime() {
		return endTime;
	}
}

public class Bike {

	// [part 1 stuff not shown here]

	// for computing rental price

	private final List<RentalInfo> rentals = new ArrayList<RentalInfo>();

	void addRentalInfo(final RentalInfo rentalInfo) {
	   rentals.add(rentalInfo);
	}

	List<RentalInfo> getRentalInfos() {
	   return new ArrayList<RentalInfo>(rentals);
	}

	void clearRentalInfos() {
	   rentals.clear();
	}
}

Vanlige feil: ...

Oppgave 2b)

Gjør nødvendige endringer av rentBike- og returnBike-metodene i BikeRental, iht. de nye kravene nevnt over, og skriv i tillegg extendRental-metoden. Baser deg på en pris på 10 kr. pr. påbegynte time (som over), 5 kr. pr. forlengelse og 10 kr. pr. for sen forlengelse eller tilbakelevering.

Expand
titleLF

I rentBike legges det til en linje som legger inn start-og sluttidspunkt for første leieperiode, i LF i form av en RentalInfo. I extendRental gjøres det tilsvarende.

Den kompliserte biten av dette er returnBike – når en skal returnere sykkelen. Da må en beregne kostnaden av leien, og trekke dette fra kunden på en eller annen måte (som vi ikke har spesifisert, og heller ikke henger oss veldig opp i). Vi har her valgt å trekke ut selve beregningen av kostnad i en egen metode, computePrice. Det gjør koden ryddigere, og forenkler å gå over til delegering i del 3.

Code Block
languagejava
linenumberstrue
    // Hjelpefunksjon for å beregne pris. 
    public int computePrice(final Person person, final Bike bike, final LocalDateTime returnTime) {
        final List<RentalInfo> rentalInfos = bike.getRentalInfos();
        int price = 0;
        LocalDateTime lastEndTime = null;

        for (final RentalInfo info : rentalInfos) {
            if (lastEndTime == null) {
                int timer = computeHours(info.getStartTime(), returnTime);
                price = 10 * timer;
            } else if (info.getEndTime().compareTo(lastEndTime) > 0) {
                price += 5;
                if (info.getStartTime().compareTo(lastEndTime) > 0) {
                    price += 10;
                }
            }
            lastEndTime = info.getEndTime();
        }
        if (returnTime.compareTo(lastEndTime) > 0) {
            price += 10;
        }
        return price;
    }

    // Find full hours of rent. Get full hours, add one.
    private int computeHours(final LocalDateTime startTime, final LocalDateTime endTime) {
        return (int) Duration.between(startTime, endTime).toHours() + 1;
    }


    public void rentBike(final Person person, final Bike bike, final LocalDateTime now, final LocalDateTime returnTime) {
        checkNowIsBeforeReturnTime(now, returnTime);
        if (bike.getRenter() != null) {
            throw new IllegalArgumentException(bike + " is currently being rented");
        }
        bike.setRenter(person);
        bike.addRentalInfo(new RentalInfo(now, returnTime)); // NEW
    }


    public void returnBike(final Person person, final Bike bike, final LocalDateTime now) {
        if (bike.getRenter() != person) {
            throw new IllegalArgumentException(bike + " isn't currently being rented by " + person);
        }
        final GeoLocation place = getStationNearby(bike, 30.0);
        if (place == null) {
            throw new IllegalArgumentException(bike + " isn't near (enough) a bike place");
        }
        // We have not specified how money is withdrawn from
// Persons. Next line is example.
        person.withdraw(computePrice(person, bike, now)); // NEW
        bike.setRenter(null);
        bike.clearRentalInfos(); // NEW
    }

    public void extendRental(final Person person, final Bike bike, final LocalDateTime now, final LocalDateTime returnTime) {
        checkNowIsBeforeReturnTime(now, returnTime);
        if (bike.getRenter() != person) {
            throw new IllegalArgumentException(bike + " isn't currently being rented by " + person);
        }
        bike.addRentalInfo(new RentalInfo(now, returnTime));
    }

Vanlige feil: ...

Oppgave 2c)

Skriv metoden printReceipt i BikeRental, som kan kalles av returnBike for å skrive ut kvitteringen iht. formatet gitt nederst i skjelettet til venstre.

Vær obs på at det eksisterer en metode formatLocalDateTime i skjelettet, som kan hjelpe.

Expand
titleLF

Den eksisterende klassen formatLocalDateTime hjelper til med å vise hvordan en kan formattere uthenting av data på HH:mm-format fra LocalDateTime – akkurat det som trengs. Siden Bike inneholder all informasjon om leieforholdet hentes dette ut herfra. Returtidspunkt får en som en parameter. Deretter kan en hente ut informasjon om starttidspunkt og antall utvidelser skrives ut ved å gå gjennom listen. Det første elementet i listen rentalInfos er første leietid, alle de andre er utvidelser.

Vi må i tillegg beregne kostnad. Dette ble gjort i en egen metode, computePrice, i 2b.

Code Block
languagejava
themeEclipse
	private void printReceipt(Person person, Bike bike, LocalDateTime returnTime) {
        final List<RentalInfo> rentalInfos = bike.getRentalInfos();
        System.out.println("Initial Rental from "+formatRentalInfoTime(rentalInfos.get(0)));
        rentalInfos.remove(0); // Fjerner den første
        for (int i = 0; i < rentalInfos.size(); i++) {
            RentalInfo re = rentalInfos.get(i);
            System.out.println("Extension "+(i+1)+" from "+formatRentalInfoTime(re));
        }
        System.out.println("Bike returned at "+
                returnTime.format(DateTimeFormatter.ofPattern("HH:mm")));
        System.out.println("Total cost: "+computePrice(person, bike, returnTime));
    }

Vanlige feil: ...



Expand
titleDel 3 – Fleksibel beregning av pris (15%)

Koden for beregning av pris er til nå hardkodet i BikeRental-klassen. Dette er noe det kan være greit å kunne endre på, f.eks. hvis en introduserer abonnement, eller man kan introdusere ulike typer fremkomstmidler med hver sin pris. Koden bør dermed gjøres mer fleksibel. Teknikken du skal bruke er delegering.


Oppgave 3a)

Forklar med tekst og/eller kode hvordan du kan bruke delegering for å gjøre det enklere å bytte ut strategien (beregningslogikken) for prising (globalt). Få med hvordan prisberegningslogikken fra tidligere blir en del av den nye løsningen. 

Hvis du ikke har fått til del 2 kan du bygge på løsningen fra del 1.

Expand
titleLF

I del 2 ble utregning av kostnad ‘hardkodet’ inn i rentBike. Hvis man skal støtte utregning av kostneder ved hjelp av delegering, så må man lage et grensesnitt (interface) som inneholder en metode for beregning av pris. I LF har vi laget grensesnittet PricePolicy, som blir implementert av klassen DefaultPricePolicy. I del 2 ble all beregning gjort i en egen hjelpemetode, computePrice. Det er derfor helt greit at grensesnittet inneholder nettopp denne metoden. I BikeRental må en så opprette et DefaultPricePolicy-objekt, og kalle dennes computePrice for å beregne pris. Hvis en vil bruke en annen prisingslogikk, så bruker man bare en annen implementasjon.

Code Block
languagejava
    PricePolicy pricePolicy = new DefaultPricePolicy();

    private int computePrice(final Person person, final Bike bike, final LocalDateTime returnTime) {
        return pricePolicy.computePrice(person, bike, returnTime);
    }

public interface PricePolicy {
    public int computePrice(Person person, Bike bike, LocalDateTime returnTime);
}


public class DefaultPricePolicy implements PricePolicy {

    protected int pricePrRental = 0, pricePrHour = 10, pricePrExtension = 5, pricePrLate = 10;

    protected void setPricePrRental(final int pricePrRental) {
        this.pricePrRental = pricePrRental;
    }

    protected void setPricePrHour(final int pricePrHour) {
        this.pricePrHour = pricePrHour;
    }

    protected void setPricePrExtension(final int pricePrExtension) {
        this.pricePrExtension = pricePrExtension;
    }

    protected void setPricePrLate(final int pricePrLate) {
        this.pricePrLate = pricePrLate;
    }

    @Override
    public int computePrice(final Person person, final Bike bike, final LocalDateTime returnTime) {
        // Implementation similar to before...
    }

    private int computeHours(final LocalDateTime startTime, final LocalDateTime endTime) {
        // Implementation similar to before...
    }
}


Oppgave 3b)

Forklar med tekst og kode hvordan en også kan bruke delegering for å tilby individuell prising (altså pr. Person), f.eks. bonus-ordninger.


Expand
titleLF

I 3a ble det lagt opp til delegering av kostnadsberegning, men dette var ikke knyttet til enkeltpersoner. For å implementere individuell prising kan en heller lage et sett med ulike PricePolicy, (studenter, barn, voksne etc.) og knyttet disse til Person-objektet (med gettere og settere). Når rentBike skal beregne kostnad må en så delegere til Person-objektet (som igjen ligger i Bike) sin PricePolicy.

Code Block
languagejava

public class Person {

    // Rest similar to before...

    private PricePolicy pricePolicy;

    public PricePolicy getPricePolicy() {
        return pricePolicy;
    }

    public void setPricePolicy(final PricePolicy pricePolicy) {
        this.pricePolicy = pricePolicy;
    }
}


public class BikeRental {

    // Rest similar to before...

    private PricePolicy pricePolicy = new DefaultPricePolicy();

    private int computePrice(final Person person, final Bike bike, final LocalDateTime returnTime) {
        PricePolicy pricePolicy = person.getPricePolicy();
        if (pricePolicy == null) { // If so, default
            pricePolicy = this.pricePolicy;
        }
        return pricePolicy.computePrice(person, bike, returnTime);
    }
}


Oppgave 3c)

En antar at det finnes ulike klasser for ulike typer fremkomstmidler en kan leie. Hver type skal kunne ha sine egne sett med verdier brukt til prising, f.eks. pris pr. time og pris pr. tidsforlengelse.

Forklar med tekst og/eller kode hvordan du kan håndtere dette, og spesielt hvordan arvingsmekanismen kan benyttes.


Expand
titleLF

Nå skal altså systemet støtte ulike typer fremkomstmidler, hver med sine priser per time og slikt. Det naturlige er da å lage en abstrakt klasse Vehicle som inneholder priselementer (pris per time, forsentbot etc) og gettere/settere (som getHourRate, setLocation m.m). Ikke alle av disse trenger å være abstrakte, eksempelvis kan det som har med lokasjon å gjøre være implementert i Vehicle. Alle fremkomstmidler (Bike, Hoverboard og slikt) og må så arve denne klassen og kan redefinere metoder for å endre verdiene/logikken som brukes i prisberegningen. Der en i BikeRental (som egentlig burde skifte navn til VehicleRental) refererer til Bike må en nå i stedet referere til den abstrakte klassen Vehicle.

Vi har fremdeles muligheten til å la ulike Personer ha individuell prising. Mens vi i oppgave 3b la opp til at PricePolicy var hardkodet med prisverdier kan en nå tenke seg at personrabatt nå gjenspeiler seg som en prosentverdi. En vanlig ting er å gi dem prosentreduksjon (som getHourRateFactor) . Vi kan lage PricePolicy for studenter, barn og voksne. I beregningen av kostnad henter vi de faktiske prisene fra fremkomstmiddelet (getHourRate), ganger det med getHourRateFactor, og ganger igjen med antall påbegynte timer. Tilsvarende kan en gjøre for alle andre verdier.

Vi har foreløpig ikke laget noe kodeeksempel for denne delen, da den kan avvike veldig basert på valgene som er gjort tidligere.



Expand
titleDel 4 – mobil-appen (15%)

Under ser du et utkast til en mobil-app for sykkelutleietjenesten, basert på FXML. Kartet til venstre viser hvor det er sykler, og én stasjon er valgt (og implisitt også en sykkel på stasjonen). Brukeren kan endre antatt tidspunkt for tilbakelevering ved å endre teksten i tekstfeltet eller bruke knappene til høyre for feltet. Den vesentlige (for denne oppgaven) FXML-koden er vist til høyre.

Image Added

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="kont2019.bike.BikeRentalController">

   ...

   <TextField fx:id="fromInput"/>

   <TextField fx:id="toInput"/>

   ...

   <Button onAction="#plus1HourAction" text="+ 1 hour"/>

   <Button onAction="#minus1HourAction" text="- 1 hour"/>

   <Button onAction="#rentAction" text="Rent"/>

</VBox>


Oppgave 4a)

Gitt denne FXML-koden, hvilke kode-elementer (klasser, felt og metoder) må finnes i koden som implementerer oppførselen (logikken) til appen?

Expand
titleLF

Av FXML-koden kan en lese at det grafiske grensesnittet (appen) knyttes til BikeRental ved hjelp av klassen BikeRentalController. Dette er i tråd med navnekonvensjonene brukt i tidligere eksamener. Det refereres til to tekstfelt som en i koden kobler seg til med henholdsvis  @FXML private TextField fromInput og toInput. Likeledes må en lage tre metoder (@FXML private void plus1HourAction, minus1HourAction og rentAction) som kalles når knappene i grensesnittet trykkes inn.

Oppgave 4b)

I kodeskjelettet er ufullstendig kode for oppførselen til appen angitt (kontroller-logikk). Fyll ut de manglende kodeelementene, som angitt i kommentarer eller markert med ???

Det er lov å anta at LocalDateTime-klassen har konstruktør og metode for konvertering fra og til String, samt metoder for å lage nye LocalDateTime-instanser et gitt antall sekunder før og etter.

Expand
titleLF

Her er det (foreløpig) lagt inn mer kode enn de ulike metodene som skulle implementeres i oppgaven.

Code Block
languagejava
public class BikeRentalController {

    private final BikeRental bikeRental;
    private final Person me;
    private final Bike bike;

    public BikeRentalController() {
        bikeRental = new BikeRental();
        final GeoLocation here = new GeoLocation(63, 10);
        bikeRental.addPlace(here);
        bike = new Bike();
        bikeRental.addBike(bike, here);
        me = new Person("Hallvard");
    }

    private final LocalTimeStringConverter localTimeStringConverter = new LocalTimeStringConverter();

    @FXML
    public void initialize() {
        setFromTime(LocalTime.now());
        setToTime(LocalTime.now().plus(1, ChronoUnit.HOURS));
        Platform.runLater(() -> toInput.requestFocus());
    }

    @FXML
    private TextField fromInput;

    @FXML
    private TextField toInput;

    /**
     * @return a LocalDataTime object corresponding to the from input field value
     */
    private LocalTime getFromTime() {
        return localTimeStringConverter.fromString(fromInput.getText());
    }
    /**
     * Updates the from input field value according to the LocalDateTime argument
     * @param time
     */
    private void setFromTime(final LocalTime time) {
        fromInput.setText(localTimeStringConverter.toString(time));
    }

    private LocalTime getToTime() {
        return localTimeStringConverter.fromString(toInput.getText());
    }
    private void setToTime(final LocalTime time) {
        toInput.setText(localTimeStringConverter.toString(time));
    }

    @FXML
    public void plus1HourAction() {
        setToTime(getToTime().plus(1, ChronoUnit.HOURS));
    }

    @FXML
    public void minus1HourAction() {
        setToTime(getToTime().minus(1, ChronoUnit.HOURS));
    }

    private LocalDateTime toLocalDateTime(final LocalTime time) {
        return LocalDateTime.now().withHour(time.getHour()).withMinute(time.getMinute());
    }

    @FXML
    public void rentAction() {
        bikeRental.rentBike(me, bike, toLocalDateTime(getFromTime()) , toLocalDateTime(getToTime()));
    }
}




Expand
titleDel 5 – Testing av BikeRental-klassen (15%)
Oppgave 5a)

Skriv kode som tester getRentedBikes i BikeRentalsom vist i skjelettet til venstre (og som ble løst i oppgave 1e).

Gjør nødvendige antakelser om metoder som brukes til konfigurasjon av BikeRental.


Expand
titleLF

Denne er foreløpig ikke testkjørt, da junit bestemte seg for å rote seg til. Vil bli sjekket og utvidet, men tanken er nå omtrent slik. Jeg har skrevet det som råtekst. (smile)

Code Block
languagejava
bikeRental = new BikeRental();
final GeoLocation here = new GeoLocation(63, 10);
bikeRental.addPlace(here);
bike = new Bike();
bikeRental.addBike(bike,here);
assertEquals(bikeRental.getRentedBikes().size(),0);
Person me = new Person("Børge");
LocalDateTime now = LocalDateTime.now();
bikeRental.rentBike(me, bike, now, now.plusHours(2));
assertEquals(bikeRental.getRentedBikes().get(0).getRenter().getName(),"Børge");
assertEquals(bikeRental.getRentedBikes().size(),1);


Oppgave 5b)

Forklar med tekst og/eller kode hvorfor det er mer komplisert å teste rentBike og returnBike enn å teste getRentedBikes. Hvilke aspekter ved disse metodene er det som gjør det mer komplisert?


Expand
titleLF

getRentedBikes kan testes ved å sjekke bare returverdier, etter å ha rigget opp diverse objekter, siden metoden ikke har side-effekter. Effekten av rentBike og returnBike er derimot endring av diverse datastrukturer, så disse må sjekkes etterpå.

Expand
titleLF

Trikset her er å skjønne at terningverdiene skal komme i samme rekkefølge som det en får fra getDieValue med stigende indekser. Her blir altså logikken helt uavhengig av hvordan terningsverdiene er representert internt i Dice-klassen.

Code Block
public class DiceIterator implements Iterator<Integer> {
	private final Dice dice;
	private int dieNum = 0;

	public DiceIterator(final Dice dice) {
		this.dice = dice;
	}

	@Override
	public boolean hasNext() {
		return dieNum < dice.getDieCount();
	}

	@Override
	public Integer next() {
		final int value = dice.getDieValue(dieNum);
		dieNum++;
		return value;
	}
}

Vanlige feil: