Модифицированный составной шаблон / Продукты питания, Рецепты и Субрецепты - PullRequest
1 голос
/ 27 марта 2020

У меня проблемы с правильной реализацией составного паттерна (чтобы сделать его наиболее эффективным).

В нем участвуют две сущности: Баси c Еда и Рецепты . Они должны быть проанализированы из файла CSV . Объект питания Basi c будет содержать букву (для обозначения, если это еда или рецепт - в данном случае f), название, калории, жир, углеводы и белок. Рецепт должен содержать букву r и название рецепта.

Я знаю, что Basi c food должен быть соответствующим листом, а Recipe будет составным. Однако строки в строке CSV файла рецептов, которые обозначают рецепт, могут содержать больше food таким образом, чтобы оно имело пары имя (имена) и количество (количество порций). Название (имена) может быть как рецептом Basi c Food, так и рецептом (Sub), в связи с чем возникает вопрос, как найти оптимальное решение?

Первое, что я хочу сделать, - это составной удерживать List<Food> хранить его композиты и иметь свойство Map<Food, Double>. Что было бы самым простым способом проверки, если мы имеем дело с определенным типом еды и предоставляем функции, подобные CRUD? Реализация программы в Java.

E.g. b, FoodName, 1, 2, 3, 4 (basic food) 
E.g. r, RecipeName, foodOneName, foodOneCount, foodTwoName, foodTwoCount, ...

Ответы [ 2 ]

1 голос
/ 31 марта 2020

Чтобы реализовать шаблон 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());
1 голос
/ 29 марта 2020

Вот возможная модель. Во-первых, давайте определим абстрактный класс, который представляет ингредиент (может быть либо пищей с базисом c, либо рецептом):

abstract class Ingredient
{
    int id;
    String name;
}

пищей с базисом c является ингредиент, поэтому он расширяет ингредиент:

class BasicFood extends Ingredient
{
    int calories;
    int fat;
    int carb;
    int protein;
}

Рецепт также является ингредиентом и содержит список предметов. Каждый предмет содержит ингредиент и количество:

class Item
{
    Ingredient ingredient;
    int quantity;
}

class Recipe extends Ingredient
{
    List<Item> items;
}
...