MiddleCodeCommonNot answered yet
Implement the behavioral pattern Strategy with a functional interface
A checkout must apply a pluggable pricing rule: the same Checkout has to work with a no-discount rule, a percentage rule and a flat-amount rule, chosen by the caller.
Constraints:
- Declare the rule as a functional interface, so a caller can pass it as a lambda.
Checkoutholds the rule by composition and extends nothing.- Adding a new rule must not change
Checkout. - Amounts are
longcents; a discount must never drive the total below zero.
@FunctionalInterface
interface DiscountRule {
// your code here
}
class Checkout {
// your code here
long total(long amountCents) {
// your code here
}
}
Write the implementation.
Give DiscountRule exactly one abstract method — long apply(long amountCents) — so any lambda or method reference is a strategy. Checkout takes the rule in its constructor, keeps it in a final field and delegates: total() returns Math.max(0, rule.apply(amountCents)). A new rule is just a new lambda at the call site, so Checkout never changes.
- ✗Adding a second abstract method to the interface, which stops it being a lambda target
- ✗Subclassing
Checkoutonce per rule instead of holding the rule as a field - ✗Switching on an enum inside
total(), so every new rule has to editCheckout
- →Why does a second abstract method stop
DiscountRulefrom being usable as a lambda? - →How does the pattern
Strategydiffer from the patternTemplate Methodhere?
Walkthrough
@FunctionalInterface
interface DiscountRule {
long apply(long amountCents); // the single abstract method → a lambda target
}
class Checkout {
private final DiscountRule rule; // the strategy is held by composition
Checkout(DiscountRule rule) {
this.rule = rule;
}
long total(long amountCents) {
return Math.max(0, rule.apply(amountCents)); // delegate, and never go below zero
}
}
// the rules are just lambdas; `Checkout` knows nothing about them
Checkout plain = new Checkout(a -> a);
Checkout percent = new Checkout(a -> a * 90 / 100);
Checkout flat = new Checkout(a -> a - 500);
Why this is Strategy:
- One abstract method.
DiscountRuleis a functional interface, so a rule can be passed as a lambda or a method reference. A second abstract method would immediately take that away. - Composition, not inheritance.
Checkoutkeeps the rule in afinalfield and delegates to it. A subclass per rule would explode combinatorially and couple every variant to the base class. - Open for extension, closed for modification. A new rule is a new lambda at the call site;
total()andCheckoutitself stay untouched. Aswitchover an enum insidetotal()would do the opposite: every new discount editsCheckout.