Backend Development 13 min read

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.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Optimizing Discount Logic with Factory and Strategy Patterns in Java

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

Backenddesign patternsJavaStrategy PatternSpringBootFactory Pattern
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.