Реализовать фильтрацию заказов через стратегию с лямбдами

15. Реализовать стратегию фильтрации заказов с помощью лямбд

Условие задачи:
Дан класс Order с полями:

  • price — стоимость заказа;

  • date — дата заказа;

  • status — статус заказа.

Необходимо реализовать универсальную фильтрацию списка заказов по разным критериям:

  • по цене;

  • по дате;

  • по статусу;

  • по комбинации нескольких условий.

Стратегию фильтрации нужно передавать в виде лямбда-выражения. При добавлении нового критерия метод фильтрации изменяться не должен.

Код:

public class Order {
    private final BigDecimal price;
    private final LocalDate date;
    private final String status;

    public Order(BigDecimal price, LocalDate date, String status) {
        this.price = price;
        this.date = date;
        this.status = status;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public LocalDate getDate() {
        return date;
    }

    public String getStatus() {
        return status;
    }
}

public class OrderService {

    public static List<Order> filterOrders(
            List<Order> orders,
            Predicate<Order> filterStrategy
    ) {
        // TODO
        return null;
    }
}

Спойлеры к решению

Подсказки
💡 Predicate<Order> представляет функцию, которая принимает Order и возвращает boolean.
💡 Конкретную стратегию можно передать непосредственно лямбда-выражением.
💡 Метод filterOrders() не должен знать, по какому именно признаку выполняется фильтрация.
💡 Несколько условий можно объединять через Predicate.and() и Predicate.or().

Решение

Метод фильтрации принимает Predicate<Order> и применяет его ко всем заказам:

public class OrderService {

    public static List<Order> filterOrders(
            List<Order> orders,
            Predicate<Order> filterStrategy
    ) {
        return orders.stream()
                .filter(filterStrategy)
                .collect(Collectors.toList());
    }
}

Теперь конкретное условие передаётся снаружи.

Фильтрация по цене:

List<Order> expensiveOrders = OrderService.filterOrders(
        orders,
        order -> order.getPrice()
                .compareTo(new BigDecimal("200.00")) > 0
);

Фильтрация по дате:

List<Order> recentOrders = OrderService.filterOrders(
        orders,
        order -> order.getDate()
                .isAfter(LocalDate.of(2023, 1, 15))
);

Фильтрация по статусу:

List<Order> newOrders = OrderService.filterOrders(
        orders,
        order -> "NEW".equalsIgnoreCase(order.getStatus())
);

Можно также создавать отдельные стратегии и комбинировать их:

Predicate<Order> delivered =
        order -> "DELIVERED".equalsIgnoreCase(order.getStatus());

Predicate<Order> afterFebruary =
        order -> order.getDate().isAfter(LocalDate.of(2023, 2, 1));

List<Order> result = OrderService.filterOrders(
        orders,
        delivered.and(afterFebruary)
);

Основной метод:

filterOrders(List<Order> orders, Predicate<Order> filterStrategy)

остаётся неизменным при появлении новых условий. Меняется только передаваемый Predicate<Order>.

Таким образом, лямбда выступает в роли конкретной стратегии, а OrderService работает с общей абстракцией Predicate<Order>.

Временная сложность фильтрации — O(n), дополнительная память — O(k) для результирующего списка из k подходящих заказов.