25T3 Week 5
Tuesday 10AM - 1PM (T10C)
Slides by Christian Tolentino (z5420628)
Assignment 2 specification is out! Please put in time to read it and get started. The finalised groups have been posted on the Microsoft Teams. Let me know if you have any issues with it.
Streams abstract away the details of data structures and allows you to access all the values in the data structure through a common interface
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] {"1", "2", "3", "4", "5"}));
for (String string : strings) {
System.out.println(string);
}
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] {"1", "2", "3", "4", "5"}));
strings.stream().forEach(x -> System.out.println(x));Map<String, Integer> map = new HashMap<>();
map.put("One", 1);
map.put("Two", 2);
map.put("Three", 3);
map.entrySet().stream().forEach(x -> System.out.printf("%s, %s\n", x.getKey(), x.getValue()));Common uses of streams are:
Sort of similar to the Array prototypes/methods in JavaScript
Streams
Convert the following to use streams
package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
for (String string : strings) {
System.out.println(string);
}
List<String> strings2 = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
List<Integer> ints = new ArrayList<Integer>();
for (String string : strings2) {
ints.add(Integer.parseInt(string));
}
System.out.println(ints);
}
}package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
// Same thing
strings.stream().forEach(x -> System.out.println(x));
// Use if there is more than one line of code needed in lambda
strings.stream().forEach(x -> {
System.out.println(x);
});
List<String> strings2 = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
List<Integer> parsedStrings = strings2.stream().map(x -> Integer.parseInt(x)).collect(Collectors.toList());
strings2.stream().map(x -> Integer.parseInt(x)).forEach(x -> System.out.println(x));
}
}Problem: Only some of the children of a parent class implement an abstract method the same way.
Question: How would you implement this without duplicating code?
Note: Behaviours are only shared downwards in inheritance
Problem: Only some of the children of a parent class implement an abstract method the same way.
We could introduce a new class in-between the parent and child class.
We could introduce a new class in-between the parent and child class.
However, this becomes problematic when the Ducks have other methods that share the same implementation.
A solution is to move this behaviour into another class and compose this class inside Duck
This is one of the main benefits of the strategy pattern, sharing behaviour across an inheritance tree
What type of design pattern is strategy?
Behavioural Pattern
Behavioural patterns are patterns concerned with algorithms and the assignment of responsibility between object
Strategy Pattern
Restaurant payment system with the following requirements:
The prices displayed on the menu are the ones for standard customers in all settings
public class Restaurant {
...
public double cost(List<Meal> order, String payee) {
switch (chargingStrategy) {
case "standard":
return order.stream().mapToDouble(meal -> meal.getCost()).sum();
case "holiday":
return order.stream().mapToDouble(meal -> meal.getCost() * 1.15).sum();
case "happyHour":
if (members.contains(payee)) {
return order.stream().mapToDouble(meal -> meal.getCost() * 0.6).sum();
} else {
return order.stream().mapToDouble(meal -> meal.getCost() * 0.7).sum();
}
case "discount":
if (members.contains(payee)) {
return order.stream().mapToDouble(meal -> meal.getCost() * 0.85).sum();
} else {
return order.stream().mapToDouble(meal -> meal.getCost()).sum();
}
default:
return 0;
}
}
...
}Not closed for modification, open for extension. If more cases need to be added, the switch statement has to be changed.
New requirements may cause the code to break or may be difficult to implement
public class Restaurant {
...
public void displayMenu() {
double modifier = 0;
switch (chargingStrategy) {
case "standard":
modifier = 1;
break;
case "holiday":
modifier = 1.15;
break;
case "happyHour":
modifier = 0.7;
break;
case "discount":
modifier = 1;
break;
}
for (Meal meal : menu) {
System.out.println(meal.getName() + " - " + meal.getCost() * modifier);
}
}
...
}Similar idea here, if new cases need to be added, the class's method itself needs to be changed. Cannot be extended
To fix these issues, we can introduce a strategy pattern and move all the individual case logic into their own classes
public interface ChargingStrategy {
/**
* The cost of a meal.
*/
public double cost(List<Meal> order, boolean payeeIsMember);
/**
* Modifying factor of charges for standard customers.
*/
public double standardChargeModifier();
}
The prices on meals often vary in different circumstances. The restaurant has four different price settings:
What type of design pattern is strategy?
Behavioural Pattern
An object (subject) maintains a list of dependents called observers. The subject notifies the observers automatically of any state changes.
Observer
Subject
Observer Pattern
In src/youtube, create a model for the following requirements of a Youtube-like video creating and watching service using the Observer Pattern: