Versions Compared

Key

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

...

Expand
titleDel 2 - Arv

Denne delen handler om hvordan arv kan utnytte at Course og Meal har en del felles.

Code Block
... class MenuItem {
    ...
}
 
// inherits from MenuItem
... class Course ... {
 
    ...
}
 
// inherits from MenuItem
... class Meal ... {
 
    ...
}
 
// updated to use MenuItem
... class Menu ... {
 
    ...
}
 
// updated to use MenuItem
... class Bill ... {
 
    ...
}
Oppgave a) MenuItem-klassen

Course- og Meal-klassene har en del felles egenskaper, som kan samles i en felles superklasse kalt MenuItem. Skriv koden til MenuItem.

Expand
titleLF

 

Oppgave b) Bruk av MenuItem

Introduksjonen av MenuItem-klassen gjør det nødvendig å endre de eksisterende klassene. Skriv ny kode for Course-, Meal-, Menu- og Bill-klassene, slik at de virker med og utnytter mulighetene som MenuItem-superklassen gir. Vurder spesielt om felt og metoder kan forenkles/slås sammen. Flett gjerne inn kommentarer til endringene og valg du gjør i koden.

Her er poenget å samle det som er felles. Klassen bør være abstract, siden det ikke gir mening å instansere den.

Code Block
public abstract class 
Expand
titleLF

Her er poenget å samle det som er felles. Klassen bør være abstract, siden det ikke gir mening å instansere den.

Code Block
public abstract class MenuItem {

	private final String name, description;

	protected MenuItem(String name, String description) {
		this.name = name;
		this.description = description;
	}

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}
}
Oppgave b) Bruk av MenuItem

Introduksjonen av MenuItem-klassen gjør det nødvendig å endre de eksisterende klassene. Skriv ny kode for Course-, Meal-, Menu- og Table-klassene, slik at de virker med og utnytter mulighetene som MenuItem-superklassen gir. Vurder spesielt om felt og metoder kan forenkles/slås sammen. Flett gjerne inn kommentarer til endringene og valg du gjør i koden.

Expand
titleLF

Poenget her at siden Course og Meal nå har en felles superklasse, så blir det enklere å ha datastrukturer med begge disse objekttypene i. Dette forenkler både Menu- og Table-klassene. En blir her bedt om å skrive mye kode på nytt, og siden eksamen var digital, så bør det være greit i praksis. Det er greit å bare skrive de delene som blir endret, til nød forklare endringene med tekst.

Code Block
public class Course extends MenuItem {
    ...
}
 
public class Meal extends MenuItem {
    ...
}

public class Menu {
	private Map<MenuItem, Double> items;

	public double getPrice(Course course) throws IllegalArgumentException {
		if (! items.containsKey(course)) {
			throw new IllegalArgumentException("This menu does not include the course " + course.getName());
		}
		return items.get(course);
	}

	public void updatePrice(MenuItem item, double price) {
		items.put(item, price);
	}

	public double getPrice(Meal meal) throws IllegalArgumentException {
		if (! items.containsKey(meal)) {
			throw new IllegalArgumentException("This menu does not include the meal " + meal.getName());
		}
		double total = items.get(meal);
		if (total == 0.0) {
			for (Course course : meal) {
				total += getPrice(course);
			}
		}
		return total;
	}
}
 
public class Table {

	private Collection<MenuItem> items = new ArrayList<>();


	// har endret fra to add-metoder til én
	public void addItem(MenuItem item) {
		this.items.add(item);
	}

	//

	private final Menu menu;

	public Table(Menu menu) {
		this.menu = menu;
	}

	public double getPrice() throws IllegalStateException {
		double total = 0.0;
		for (MenuItem item : items) {
			try {
				// note: cannot use menu.getPrice(item), since there is no menu.getPrice(MenuItem) method
				if (item instanceof Course) {
					total += menu.getPrice((Course) item);
				} else if (item instanceof Meal) {
					total += menu.getPrice((Meal) item);
				}
			} catch (IllegalArgumentException e) {
				throw new IllegalStateException(e);
			}
		}
		return total;
	}
}

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

 

Expand
titleDel 3 - Objektsamhandling

Denne delen handler om samhandling mellom Table-klassen og en ny Kitchen-klasse.

Code Block
public class Table {
 
    ...
 
    /**
     * Sets the Kitchen that should be notified when items are added.
     * Note that this method may be called several times with different Kitchen objects.
     * @param kitchen
     */
    public void setKitchen(Kitchen kitchen) {
        ... what should be done here? ...
    }
 
    ...
}
 
/**
 * Interface for classes that want to know when Courses have been produced by a Kitchen.
 */
public interface KitchenListener {
    public void courseReady(Table table, Course course);
}
 
/**
 * Manages a queue of courses to produce, based on what is requested by Tables.
 */
public class Kitchen {
 
    // for each Table that has requested Courses,
    // there is a Collection of the those that are yet to be made
    private Map<Table, Collection<Course>> courseQueue = new HashMap<Table, Collection<Course>>();
    
    /**
     * Enqueues a Course in the production queue, that is part of the provided Table.
     * @param table
     * @param course
     */
    private void produceCourse(Table table, Course course) {
        Collection<Course> courses = courseQueue.get(table);
        ... what should be done the first time a Table requests a Course? ...
        courses.add(course);
        courseQueue.put(table, courses);
    }
 
    /**
     * Internal methods that must be called when a Course of a Table has been produced.
     * Notifies registered listeners about the event.
     * @param table
     * @param course
     */
    private void courseProduced(Table table, Course course) {
        Collection<Course> courses = courseQueue.get(table);
        courses.remove(course);
        ... what should be done here, to support observers? ...
    }
 
    /**
     * Should be called when a MenuItem is added to a Table,
     * so the corresponding Courses can be produced.
     * @param table
     * @param item
     */
    public void menuItemAdded(Table table, MenuItem item) {
        ... handle cases when item is a Course or a Meal ...
    }
 
    //
    
    ... fields and methods for supporting observers ...
}
Oppgave a) Kobling fra Table til Kitchen

Bestilling av mat henger sammen med laging av mat, og nå skal det innføres en Kitchen-klasse (se utgitt kode), som har en dobbel kobling til Table-klassen. Denne deloppgaven handler om den første koblingen, nemlig at et Table-objekt skal kunne ha en kobling til ett Kitchen-objekt, som skal få beskjed når en ny rett (Course) eller et nytt måltid (Meal) legges til i lista over bestillinger. Tenk på det som en bestilling fra kelneren som betjener bordet til kjøkkenet om hvilke retter som skal lages.

Fullfør Table-klassen med det som trengs for å håndtere en kobling til Kitchen og gi beskjed til Kitchen-objektet om retter og måltider som bestilles.

Expand
titleLF

Vi trenger et felt for Kitchen og en setter (oppgitt i koden). Vi må også si fra til Kitchen-objektet om nye bestillinger i addItem-metoden.

Code Block
private Kitchen kitchen;


// setter
public void setKitchen(Kitchen kitchen) {
	... her kommer det mere siden ...
	this.kitchen = kitchen;
	... her kommer det mere siden ...
}

// må si fra til kjøkkenet (hvis satt) når noe bestilles
public void addItem(MenuItem item) {
	this.items.add(item);
	if (kitchen != null) {
		kitchen.menuItemAdded(this, item);
	}
}
Oppgave b) Kobling fra Kitchen til Table og andre klasser.

Table-objektene representerer bordene, og de må få beskjed når en rett er ferdiglaget av kjøkkenet. Derfor må Kitchen-klassen være kodet for å støtte en slik varsling. For å gjøre koden mer generell, så brukes observert-observatør-teknikken (se utgitt kode). Fullfør Kitchen-klassen, så den kan fungere som en observert og Table-klassen som observatør vha. KitchenListener-grensesnittet.

Expand
titleLF

 Dette er standard bruk av observatør-observert-teknikken, altså må en ha felt med liste av lyttere, metoder for å legge til og fjerne lyttere og helst en hjelpemetode for å varsle dem.

Code Block
public class Kitchen {
 
	...
 
	private Collection<KitchenListener> kitchenListeners = new ArrayList<KitchenListener>();

	public void addKitchenListener(KitchenListener listener) {
		kitchenListeners.add(listener);
	}

	public void removeKitchenListener(KitchenListener listener) {
		kitchenListeners.remove(listener);
	}

	private void fireCourseReady
Expand
titleDel 3 - Objektsamhandling

Denne delen handler om samhandling mellom Table-klassen og en ny Kitchen-klasse.

Code Block
public class Table { ... /** * Sets the Kitchen that should be notified when items are added. * Note that this method may be called several times with different Kitchen objects. * @param kitchen */ public void setKitchen(Kitchen kitchen) { ... what should be done here? ... } ... } /** * Interface for classes that want to know when Courses have been produced by a Kitchen. */ public interface KitchenListener { public void courseReady(Table table, Course course); } /** * Manages a queue of courses to produce, based on what is requested by Tables. */ public class Kitchen { // for each Table that has requested Courses, // there is a Collection of the those that are yet to be made private Map<Table, Collection<Course>> courseQueue = new HashMap<Table, Collection<Course>>(); /** * Enqueues a Course in the production queue, that is part of the provided Table. * @param table * @param course */ private void produceCourse(Table table, Course course) { Collection<Course> courses = courseQueue.get(table); ... what should be done the first time a Table requests a Course? ... courses.add(course); courseQueue.put(table, courses); } /** * Internal methods that must be called when a Course of a Table has been produced. * Notifies registered listeners about the event. * @param table * @param course */ private void courseProduced
(Table table, Course course) {
Collection<Course> courses = courseQueue.get(table); courses.remove(course); ... what should be done here, to support observers? ... } /** * Should be called when a MenuItem is added to a Table, * so the corresponding Courses can be produced. * @param table * @param item */ public void menuItemAdded(Table table, MenuItem item) { ... handle cases when item is a Course or a Meal ... } // ... fields and methods for supporting observers ... }
Oppgave a) Kobling fra Table til Kitchen

Bestilling av mat henger sammen med laging av mat, og nå skal det innføres en Kitchen-klasse (se utgitt kode), som har en dobbel kobling til Table-klassen. Denne deloppgaven handler om den første koblingen, nemlig at et Table-objekt skal kunne ha en kobling til ett Kitchen-objekt, som skal få beskjed når en ny rett (Course) eller et nytt måltid (Meal) legges til i lista over bestillinger. Tenk på det som en bestilling fra kelneren som betjener bordet til kjøkkenet om hvilke retter som skal lages.

Fullfør Table-klassen med det som trengs for å håndtere en kobling til Kitchen og gi beskjed til Kitchen-objektet om retter og måltider som bestilles.

Expand
titleLF

 

Oppgave b) Kobling fra Kitchen til Table og andre klasser.

Table-objektene representerer bordene, og de må få beskjed når en rett er ferdiglaget av kjøkkenet. Derfor må Kitchen-klassen være kodet for å støtte en slik varsling. For å gjøre koden mer generell, så brukes observert-observatør-teknikken (se utgitt kode). Fullfør Kitchen-klassen, så den kan fungere som en observert og Table-klassen som observatør vha. KitchenListener-grensesnittet.

Expand
titleLF
 
Oppgave c) Kitchen sin Course-kø

Kitchen-klassen (se utgitt kode) håndterer en kø av retter, organisert etter hvilket bord (Table) som har bestilt den. Logikken er bare delvis implementert, fullfør koden for metodene produceCourse, courseProduced og menuItemAdded så køen håndteres riktig, ved å fylle inn der det er angitt med ...

Expand
titleLF
		for (KitchenListener listener : kitchenListeners) {
			listener.courseReady(table, course);
		}
	}
}

Table må implementere lyttergrensesnittet og dermed også varslingsmetoden(e), og en må huske å registrere den når Kitchen-objektet settes. Dette kompliserer setteren, siden den både må avregistrere seg fra det forrige Kitchen-objektet og registrere seg på det nye.

Code Block
public class Table implements KitchenListener {
 
	...
 
	public void setKitchen(Kitchen kitchen) {
		if (this.kitchen != null) {
			this.kitchen.removeKitchenListener(this);
		}
		this.kitchen = kitchen;
		if (this.kitchen != null) {
			this.kitchen.addKitchenListener(this);
		}		
	}
 
	@Override
	public void courseReady(Table table, Course course) {
		// reaksjon på varslingen
	}
}
Oppgave c) Kitchen sin Course-kø

Kitchen-klassen (se utgitt kode) håndterer en kø av retter, organisert etter hvilket bord (Table) som har bestilt den. Logikken er bare delvis implementert, fullfør koden for metodene produceCourse, courseProduced og menuItemAdded så køen håndteres riktig, ved å fylle inn der det er angitt med ...

Expand
titleLF
Code Block
private void produceCourse(Table table, Course course) {
	Collection<Course> courses = courseQueue.get(table);
	if (courses == null) {
		courses = new ArrayList<Course>();
		courseQueue.put(table, courses);
	}
	courses.add(course);
}

private void courseProduced(Table table, Course course) {
	Collection<Course> courses = courseQueue.get(table);
	if (courses != null) {
		courses.remove(course);
		fireCourseReady(table, course);
	}
}

public void menuItemAdded(Table table, MenuItem item) {
	if (item instanceof Course) {
		produceCourse(table, (Course) item);
	} else if (item instanceof Meal) {
		for (Course course : (Meal) item) {
			produceCourse(table, course);
		}			
	}
}

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

 

Expand
titleDel 4 - Diverse
Code Block
public class Meal {
    ...
    
    /**
     * Finds a Course satisfying the provided Predicate.
     * @param test
     * @return the first Course satisfying the provided Predicate.
     */
    public Course findCourse(Predicate<Course> test) {
        ... what should be done here? ...
    }
}

public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     * @param t the input argument
     * @return true if the input argument matches the predicate, otherwise false
     */
    boolean test(T t);
}

public class MealTest extends TestCase {

    private Course c1, c2;
    private Meal meal;

    @Override
    protected void setUp() throws Exception {
        ... what should be done here? ...
    }
    
    /**
     * Tests Meal's support for foreach loop (iteration)
     */
    public void testIteration() {
        ... test code ...
    }
    
    /**
     * Tests Meal's findCourse(Prediate) method
     */
    public void testFindCourse() {
        ... test code ...
    }
}
Oppgave a) Predicate-grensesnittet

Hva kalles et slikt grensesnitt som Predicate (se utgitt kode) og hvorfor?

Expand
titleLF

 

) og hvorfor?

Expand
titleLF

Dette er et såkalt funksjonelt grensesnitt, siden det har én metode som (er ment som å) oppfører seg som en matematisk funksjon.

Oppgave b) findCourse-metoden

Meal skal utvides med en metode for å finne Course-objekter som tilfredsstiller gitte betingelser. For å gjør koden generell så gis betingelsen inn som argument i form av et Predicate-objekt (se utgitt kode). Fullfør findCourse-metoden (se utgitt kode).

Expand
titleLF
 

Predicate-objekt (se utgitt kode). Fullfør findCourse-metoden (se utgitt kode).

Expand
titleLF

Vi må løpe gjennom course-lista og returnere det første Course-objektet som Predicate-objektet svarer true for.

Code Block
public Course findCourse(Predicate<Course> test) {
	for (Course course : courses) {
		if (test.test(course)) {
			return course; 
		}
	}
	return null;
}
Oppgave c) Testing av Meal

Den utgitte koden inneholder en JUnit-test for testing av Meal-klassen. To typer funksjonalitet skal testes, 1) metoden(e) for å støtte iterasjon vha. foreach-løkker og 2) findCourse-metoden. Fullfør koden. Hvis du ikke får til å skrive riktig kode for JUnit-rammeverket, så forklar med tekst og kode hvordan du ville gjort det.

Expand
titleLF
 

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