Optimizing Discount Logic with Factory and Strategy Patterns in Java
This article demonstrates how to replace bulky if‑else discount handling in a SpringBoot shopping service with a clean combination of strategy and factory patterns, providing extensible, maintainable code for various user types and product discounts.
The author starts by pointing out the problems of using massive if‑else statements for discount calculations in a Java 21, SpringBoot 3.3.0 project, noting violations of the Open/Closed Principle and code bloat.
To address this, a cache layer is introduced to preload user and merchandise data into maps, with sample UserCache and MerchandiseCache implementations provided.
@Slf4j
@Component
public class UserCache extends AbstractCache
{
private final Map
userMap = new HashMap<>();
@Override
public void init() {
this.userMap.put(1L, User.builder().id(1L).name("Lucy").gender("女").age(18).type(0).build());
// ... other users ...
log.info("user cache init success: {}", JSON.toJSONString(this.userMap));
}
@Override
public User get(Long key) { return this.userMap.get(key); }
@Override
public void clear() { this.userMap.clear(); }
} @Slf4j
@Component
public class MerchandiseCache extends AbstractCache
{
private final Map
merchandiseMap = new HashMap<>();
@Override
public void init() {
this.merchandiseMap.put(1L, Merchandise.builder().id(1L).name("小米14").price(BigDecimal.valueOf(3999.00)).build());
// ... other merchandise ...
log.info("merchandise cache init success: {}", JSON.toJSONString(this.merchandiseMap));
}
@Override
public Merchandise get(Long key) { return this.merchandiseMap.get(key); }
@Override
public void clear() { this.merchandiseMap.clear(); }
}An enum DiscountEnum defines discount rates for different user types.
@Getter
public enum DiscountEnum {
NORMAL(0, "普通用户", BigDecimal.valueOf(1.00)),
VIP(1, "普通VIP", BigDecimal.valueOf(0.95)),
PVIP(2, "高级VIP", BigDecimal.valueOf(0.90)),
EVIP(4, "至尊VIP", BigDecimal.valueOf(0.85)),
SVIP(3, "超级VIP", BigDecimal.valueOf(0.80));
// fields, constructor, and valueOf method omitted for brevity
}First, the author refactors the original if‑else discount logic into a simple ShoppingStrategyAware interface and concrete strategy classes for each user type (e.g., NormalStrategy , VIPStrategy ).
public interface ShoppingStrategyAware {
Map
buy(User user, Merchandise merchandise);
DiscountEnum getStrategy();
}Each strategy implements the interface, encapsulating the specific discount calculation and returning a result map.
@Slf4j
@Component
public class NormalStrategy implements ShoppingStrategyAware {
@Override
public Map
buy(User user, Merchandise merchandise) {
DecimalFormat df = new DecimalFormat("0.00");
Map
result = new HashMap<>();
result.put("用户姓名", user.getName());
result.put("商品名称", merchandise.getName());
result.put("商品原价", df.format(merchandise.getPrice()));
result.put("折后价格", df.format(merchandise.getPrice().multiply(getStrategy().getDiscount())));
return result;
}
@Override
public DiscountEnum getStrategy() { return DiscountEnum.NORMAL; }
}To avoid injecting each strategy manually, a ShoppingFactory is introduced. It collects all ShoppingStrategyAware beans, registers them in a map keyed by DiscountEnum , and provides a getStrategy(int code) method.
@Slf4j
@Component
public class ShoppingFactory {
@Resource
private List
shoppingStrategies;
private final Map
shoppingMap = new HashMap<>();
@PostConstruct
public void register() {
shoppingStrategies.forEach(s -> shoppingMap.put(s.getStrategy(), s));
}
public ShoppingStrategyAware getStrategy(int code) {
DiscountEnum discountEnum = DiscountEnum.valueOf(code);
return shoppingMap.get(discountEnum);
}
}Finally, the service layer uses the factory to obtain the appropriate strategy based on the user's type, eliminating the need for any further if‑else statements.
@Service("shoppingService3")
public class ShoppingService3Impl implements ShoppingService {
@Resource
private ShoppingFactory shoppingFactory;
@Resource
private UserCache userCache;
@Resource
private MerchandiseCache merchandiseCache;
@Override
public Map
buy(Long userId, Long merchandiseId) {
User user = userCache.get(userId);
Merchandise merchandise = merchandiseCache.get(merchandiseId);
ShoppingStrategyAware strategy = shoppingFactory.getStrategy(user.getType());
return strategy.buy(user, merchandise);
}
}The article concludes that this combination of strategy and factory patterns yields a clean, extensible design where adding new user types only requires creating a new strategy class, without touching existing business logic.
Source code repository: https://github.com/hanjx369/shopping
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.