MiddleКодЧастоЕщё не отвечали
Реализуйте поведенческий паттерн Strategy через функциональный интерфейс
Касса должна применять подключаемое правило цены: один и тот же Checkout обязан работать и без скидки, и с процентной скидкой, и с фиксированной — правило выбирает вызывающий код.
Ограничения:
- Объявите правило функциональным интерфейсом, чтобы вызывающий код передавал его лямбдой.
Checkoutдержит правило композицией и ни от чего не наследуется.- Добавление нового правила не должно менять
Checkout. - Суммы —
longв центах; скидка никогда не уводит итог ниже нуля.
@FunctionalInterface
interface DiscountRule {
// your code here
}
class Checkout {
// your code here
long total(long amountCents) {
// your code here
}
}
Допишите реализацию.
Дайте DiscountRule ровно один абстрактный метод — long apply(long amountCents), — тогда стратегией становится любая лямбда или ссылка на метод. Checkout принимает правило в конструкторе, держит его в final поле и делегирует: total() возвращает Math.max(0, rule.apply(amountCents)). Новое правило — просто новая лямбда в месте вызова, и Checkout не меняется.
- ✗Добавлять в интерфейс второй абстрактный метод, из-за чего он перестаёт годиться для лямбды
- ✗Заводить по подклассу
Checkoutна каждое правило вместо хранения правила полем - ✗Делать
switchпоenumвнутриtotal(), из-за чего каждое новое правило правитCheckout
- →Почему второй абстрактный метод не даёт использовать
DiscountRuleкак лямбду? - →Чем паттерн
Strategyотличается здесь от паттернаTemplate Method?
Разбор
@FunctionalInterface
interface DiscountRule {
long apply(long amountCents); // единственный абстрактный метод → цель для лямбды
}
class Checkout {
private final DiscountRule rule; // стратегия хранится композицией
Checkout(DiscountRule rule) {
this.rule = rule;
}
long total(long amountCents) {
return Math.max(0, rule.apply(amountCents)); // делегируем и не уходим ниже нуля
}
}
// правила — просто лямбды, `Checkout` о них ничего не знает
Checkout plain = new Checkout(a -> a);
Checkout percent = new Checkout(a -> a * 90 / 100);
Checkout flat = new Checkout(a -> a - 500);
Почему это Strategy:
- Один абстрактный метод.
DiscountRule— функциональный интерфейс, поэтому правило передаётся лямбдой или ссылкой на метод. Второй абстрактный метод сразу лишил бы его этой возможности. - Композиция вместо наследования.
Checkoutдержит правило вfinalполе и делегирует ему. Подкласс на каждое правило дал бы комбинаторный взрыв классов и жёсткую связь с базовым классом. - Открыт для расширения, закрыт для изменения. Новое правило — новая лямбда у вызывающего кода;
total()и самCheckoutне меняются.switchпоenumвнутриtotal()дал бы обратное: каждая новая скидка правитCheckout.