Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Expand
titleDel 1 - Grunnleggende klasser

Tema for alle oppgavene er en restaurant, med fokus på menyen. Course-klassen representerer en enkeltrett, f.eks. laksemousse, biffsnadder eller karamellpudding. Meal representerer et ferdig sammensatt måltid som bestilles som et hele, f.eks. en tre-retters med laksemousse, biffsnadder og karamellpudding. Menu er menyen med utvalget av retter og måltider og deres priser. Table representerer bordet, i denne sammenheng bestillingene.

Under finner du utgitt kode, som fungerer som kontekst for oppgavene. Merk spesielt kommentarene til metoder, som inneholder krav til metodene, og linjer med ..., som indikerer manglende kode.

Code Block
public class Course {
 
    public final String name, description;
 
    public Course(String name, String description) {
        super();
        ... initialization ...
    }
}
 
/**
 * Represents a set of (pre-defined) Courses that are ordered as a whole
 */
public class Meal {
 
    private final String name, description;
 
    public Meal(String name, String description, Course[] courses) {
        super();
        ... initialization ...
        this.courses = Arrays.asList(courses);
    }
    
    public String getName() {
        return name;
    }
    
    public String getDescription() {
        return description;
    }
}
 
/**
 * Manages the set of Courses and Meals offered and their prices.
 */
public class Menu {
 
    ... fields and methods ...
 
    /**
     * Gets the price for a Course.
     * @param course
     * @return the price
     * @throws IllegalArgumentException if this Menu doesn't include the provided Course
     */
    public double getPrice(Course course) throws IllegalArgumentException {
        ....
    }
 
    /**
     * Sets/changes the price of the provided Course.
     * @param course
     * @param price
     */
    public void updatePrice(Course course, double price) {
        ...
    }
    
    /**
     * Gets the price for a Meal. If the registered price is 0.0,
     * the price is computed as the sum of the prices of the Meal's courses.
     * @param meal
     * @return
     * @throws IllegalArgumentException if this Menu doesn't include the provided Meal,
     *  or if a price of a Course is needed, but is missing
     */
    public double getPrice(Meal meal) throws IllegalArgumentException {
        ...
    }    
 
    /**
     * Sets/changes the price of the provided Meal.
     * @param meal
     * @param price
     */
    public void updatePrice(Meal meal, double price) {
        ...
    }
}

/**
 * Manages the set of ordered items for a table (set of guests).
 */
public class Table {
 
    ... fields and methods ...
 
    /**
     * Initializes a new Table with a Menu that provides the prices for the Courses and Meals
     * @param menu
     */
    public Table(Menu menu) {
        ...
    }
    /**
     * Computes the total price for all the added items. Prices are provided by the Menu.
     * @return the total price
     * @throws IllegalStateException when the price of an item cannot be provided by the Menu
     */
    public double getPrice() throws IllegalStateException {
        ...
    }
}
(5p) Oppgave a) Innkapsling av navn og beskrivelse

Course- og Meal-klassene representerer henholdsvis enkeltretter og måltider sammensatt av flere retter, slik vi finner i menyer på spisesteder.

Både Course og Meal skal initialiseres med navn og beskrivelse, som siden ikke skal kunne endres. I den utgitte koden er det brukt to varianter for å håndtere dette. Course har public-felt og ingen get- eller set-metoder, mens Meal har private-felt og public get-metoder. Angi fordeler og ulemper med hver kodingsteknikk. Hvilken anbefaler du? Begrunn svaret!

Expand
titleLF

Dette handler om innkapsling, som har to aspekter: 1) sikring av gyldig tilstand og 2) skjuling av implementasjonsdetaljer, så koden lettere kan endres uten at andre klasser påvirkes. Av disse to er aspekt 1) viktigst.

Den første teknikken krever mindre kode og er derfor enklere å lese og skrive. Siden en bruker final-modifikatoren så sikres aspekt 1), siden verdiene/tilstanden ikke kan endres tross at feltet er public. Imidlertid så er det andre aspektet ved innkapsling ikke ivaretatt, siden feltet er eksponert. Derfor er den andre teknikken å foretrekke, som ivaretar begge aspektene.

(5p) Oppgave b) super()

I konstruktørene til Course og Meal står det super() i første linje. Hva betyr/gjør denne linja? Hva ville skjedd om den ble fjernet?

Expand
titleLF
super() kaller konstruktøren i superklassen og trengs for å sikre at også superklassens konstruktør blir kjørt. Her er super-klassen implisitt Object-klassen. Hvis linja ikke er med, så vil et tilsvarende kall, altså til en konstruktør uten argumenter, bli lagt til av kompilatoren. Derfor kan vi trygt fjerne linja.
(5p) Oppgave c) courses-feltet i Meal

I konstruktøren for Meal er det lagt til ei linje for initialisering av feltet courses, som lagrer matrettene som måltidet er satt sammen av. Skriv en passende deklarasjon for courses, slik at feltet er egnet til formålet og initialiseringslinja blir riktig.

Expand
titleLF

Her er poenget å velge en type som passer til hvordan feltet brukes og verdien den blir tilordnet. Verdien som tilordnes er av typen List<Course>, så typen må enten være List eller en av dens superklasser, som er Collection og Iterable. Hvis vi bare trenger metodene i Collection, som List arver fra, så er det bedre å bruke Collection i deklarasjonen.

private Collection<Course> courses;

(5p) Oppgave d) Arrays.asList-metoden

Initialiseringskoden bruker metoden Arrays.asList. Skriv en (mulig) metodedeklarasjon (altså den første linja i koden for metoden, med modifikatorer og alt som trengs) for denne metoden, som passer til slik metoden brukes.

Expand
titleLF

Her er poenget å ha riktig argument- og resultat-type, henholdsvis en Course-array og Course-liste, samt merke seg at metoden kalles ved klassenavnet foran og derfor trenger static-modifikatoren.

public static List<Course> asList(Course[] courses)

Det går an å bruke varargs-notasjonen (se Varargs), slik den har i virkeligheten, men det er ikke påkrevd.

(2p) Oppgave e) Initialisering av courses-feltet

I initialiseringskoden brukes this.courses = ... Hva ville skjedd om vi utelot this-nøkkelordet, altså bare skrev courses = ... ? Ta i betraktning dine svar på tidligere delspørsmål!

Velg ett alternativ
  • varsel om mulig feil (gul strek i editoren) 
  • kræsj (unntak) ved kjøring
  • logisk feil ved kjøring
  • det virker som det skal

 

Expand
titleLF
feilmelding ved kompilering (rød strek i editoren)
(3p) Oppgave f) Initialisering av courses-feltet forts.

Begrunn/forklar ditt valg i forrige deloppgave!

Expand
titleLF
Uten this. foran vil venstresiden referere til metode-argumentet av typen Course[]. Linja vil da prøve å tilordne en (referanse til en) List<Course> til en Course[]-variabel, som ikke er lov.
 
(7p) Oppgave g) Innkapsling av Course-objekter i Meal-klassen

Skriv nødvendig kode for å innkapsle Course-objektene i Meal-klassen. Det skal være mulig å legge til og fjerne Course-objekter, samt iterere (løpe gjennom) alle Course-objektene i et Meal-objekt med kode slik som dette:

for (Course course : meal) {
   ... gjør noe med course her ...
}

Expand
titleLF

For å legge til og fjerne Course-objekter, så trengs addCourse- og removeCourse-metoder som tar inn et Course-argument. For å kunne iterere med et Meal-objekt bak kolonet i en for-løkke, så må Meal-klassen implementere Iterable<Course> og derfor ha en iterator()-metode som returnerer Iterator<Course>.

Code Block
public class Meal implements Iterable<Course> {

	...

	private Collection<Course> courses;

	//
	
	public void addCourse(Course course) {
		this.courses.add(course);
	}
	
	public void removeCourse(Course course) {
		this.courses.remove(course);
	}

	// implement Iterable<Course>

	@Override
	public Iterator<Course> iterator() {
		return courses.iterator();
	}
}
(10p) Oppgave h) Menu-klassen

Menu-klassen skal kunne lagre et sett Course- og Meal-objekter og deres priser. Skriv ferdig klassen, med nødvendige felt og metoder for dette.

Expand
titleLF

Her er problemet å finne en god måte å representere settet med Course- og Meal-objekter OG deres priser. En Map passer til dette, siden den kobler et sett med objekter til hvert sitt andre objekt, her prisen. Vi velger å bruke to Map-objekter, etter for hver objektklasse (Course og Meal). Vi kunne strengt tatt klart oss med én, som var spesialisert til Object, tilsvarende slik vi gjør senere, når MenuItem introduseres som en felles superklasse. En alternativ teknikk er å bruke to sett med to lister, hvor prisen er på samme indeks som tilsvarende Course eller Meal-objekt, men det er mer knot.

Code Block
private Map<Course, Double> courses;
private Map<Meal, Double> meals;

public double getPrice(Course course) throws IllegalArgumentException {
   if (! courses.containsKey(course)) {
      throw new IllegalArgumentException("This menu does not include the course " + course.name);
   }
   return courses.get(course);
}
 
public void updatePrice(Course course, double price) {
   courses.put(course, price);
}
 
public double getPrice(Meal meal) throws IllegalArgumentException {
   if (! meals.containsKey(meal)) {
      throw new IllegalArgumentException("This menu does not include the meal " + meal.getName());
   }
   double total = meals.get(meal);
   if (total == 0.0) {
      for (Course course : meal) {
         total += getPrice(course);
      }
   }
   return total;
}
 
public void updatePrice(Meal meal, double price) {
   meals.put(meal, price);
}
(10p) Oppgave i) Table-klassen

Table-klassen representerer alle retter (Course) og måltider (Meal) som gjestene ved et bord har bestilt. For å kunne beregne (total)prisen, er Table-klassen knyttet til en Menu.

Skriv ferdig klassen.

Expand
titleLF

Vi bruker to lister, én for hver objekt-klasse (Course og Meal). Menu-objektet som trengs for å beregne priser, tas inn i konstruktøren. Ved beregning av pris, så må en fange opp evt. unntak av typen IllegalArgumentException og utløse dem på nytt som IllegalStateException, slik at unntakstypen blir som deklarert.

Code Block
private Collection<Course> courses = new ArrayList<>();
private Collection<Meal> meals = new ArrayList<>();
 
public void addCourse(Course course) {
   this.courses.add(course);
}
 
public void addMeal(Meal meal) {
   this.meals.add(meal);
}

private final Menu menu;
 
public Table(Menu menu) {
   this.menu = menu;
}
 
public double getPrice() throws IllegalStateException {
   double total = 0.0;
   for (Course course : courses) {
      try {
         total += menu.getPrice(course);
      } catch (IllegalArgumentException e) {
         throw new IllegalStateException(e);
      }
   }
   for (Meal meal : meals) {
      try {
         total += menu.getPrice(meal);
      } catch (IllegalArgumentException e) {
         throw new IllegalStateException(e);
      }
   }
   return total;
}

Koden finnes på github, se del 1 av konten 2017

...