Чтобы реализовать шаблон Composite, вам необходим интерфейс, который реализуется как Food
, так и Recipe
, чтобы обрабатывать их единообразно.
Этот интерфейс, например, может предоставлять методы для получения характеристики продукта или рецепта:
public interface Component {
String getName();
double getCalories();
double getFat();
double getCarb();
double getProtein();
}
Для Food
реализация этого интерфейса проста, поскольку он просто совпадает с его получателями:
public class Food implements Component {
private String name;
private double calories;
private double fat;
private double carb;
private double protein;
public Food(String name, double calories, double fat, double carb, double protein) {
this.name = name;
this.calories = calories;
this.fat = fat;
this.carb = carb;
this.protein = protein;
}
@Override
public String getName() {
return name;
}
@Override
public double getCalories() {
return calories;
}
@Override
public double getFat() {
return fat;
}
@Override
public double getCarb() {
return carb;
}
@Override
public double getProtein() {
return protein;
}
}
В Recipe
, как Вы сказали, что мы можем использовать Map
для сохранения его компонентов и количеств.
Для реализации интерфейса Component
мы можем вернуть сумму (умноженную на количество) характеристик всех его компонентов, что может быть как еда, так и суб-рецепты. Но из-за полиморфизма нам не нужно обрабатывать их по-разному, что является целью шаблона Composite:
public class Recipe implements Component {
private String name;
private Map<Component, Double> components;
public Recipe(String name) {
this.name = name;
components = new HashMap<>();
}
@Override
public String getName() {
return name;
}
@Override
public double getCalories() {
return componentsSum(Component::getCalories);
}
@Override
public double getFat() {
return componentsSum(Component::getFat);
}
@Override
public double getCarb() {
return componentsSum(Component::getCarb);
}
@Override
public double getProtein() {
return componentsSum(Component::getProtein);
}
private double componentsSum(Function<Component, Double> function) {
return components.entrySet().stream()
// get the requested characteristic and multiply it by the quantity
.mapToDouble(entry -> function.apply(entry.getKey()) * entry.getValue())
.sum();
}
}
В Recipe
мы также добавляем операции CRUD для добавления / удаления / обновления его компонентов. Например:
public Double add(Component component, double quantity) {
// if already present, add the quantity
return components.merge(component, quantity, Double::sum);
}
public Double remove(Component component) {
return components.remove(component);
}
public Double update(Component component, double quantity) {
// if present, replace the quantity
return components.computeIfPresent(component, (k, v) -> quantity);
}
Вам необходимо переопределить equals
и hashCode
для Food
и Recipe
, чтобы эти методы работали правильно. В моих примерах мы должны рассматривать только переменную name
.
, а затем код для ее проверки:
// foods
Component f1 = new Food("f1", 1, 1, 1, 1);
Component f2 = new Food("f2", 2, 2, 2, 2);
Component f3 = new Food("f3", 3, 3, 3, 3);
// recipe with only food
Recipe r1 = new Recipe("r1");
r1.add(f1, 2);
r1.add(f2, 1);
// recipe with food and sub-recipe
Recipe r2 = new Recipe("r2");
r2.add(f3, 1);
r2.add(r1, 2);
// prints 11.0 11.0 11.0 11.0
System.out.println(r2.getCalories() + " " +r2.getFat() + " " + r2.getCarb() + " " + r2.getProtein());