Versions Compared

Key

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

...

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.

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

...

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
themeEclipsecollapsetrue
	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
collapsetrue
    PricePolicy pricePolicy = new DefaultPricePolicy();

    private int computePrice(final Person person, final Bike bike, final LocalDateTime returnTime) {
//    PricePolicy pricePolicy = person.getPricePolicy(); // Gammel
        PricePolicy pricePolicy = new DefaultPricePolicy();
        final int price =  return pricePolicy.computePrice(person, bike, returnTime);
        return price;
    }



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;
        }
        final int price = return pricePolicy.computePrice(person, bike, returnTime);
        return price;
    }
}


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.


...