Refactor Payment Logic: Replace if‑else with Java Enums & Strategy

This article demonstrates how to replace cumbersome if‑else chains in payment processing with Java enums that encapsulate behavior, leveraging functional interfaces, the strategy pattern, and Spring integration to achieve cleaner, extensible, and testable code while adhering to the Open/Closed principle.

Architect
Architect
Architect
Refactor Payment Logic: Replace if‑else with Java Enums & Strategy

1. Pain Point Analysis: Where the if‑else Chain Fails

We first examine the symptoms of long if‑else chains: poor readability, low extensibility, violation of the Open/Closed principle, and difficulty testing.

Readability : logic is scattered, hard to see supported branches.

Extensibility : adding a new type requires modifying existing code, risking bugs.

Open/Closed Violation : not truly open for extension and closed for modification.

Testing Difficulty : all logic packed in one method makes unit tests cumbersome.

These problems are ideal for a combination of enum, strategy pattern, and functional programming.

2. Solution: Enums Are More Than Constant Collections

Java enums are full‑featured classes that can hold fields, define constructors, implement methods, and contain behavior via functional interfaces.

Fields

Constructors

Methods

Behavior (functional interface)

3. Practical Example: Refactor Payment Channel Selection with Enums

Correct Approach 1: Enum Holds Behavior (Functional Interface)

Define a functional interface for payment actions:

@FunctionalInterface
public interface PayHandler {
    void pay(BigDecimal amount);
}

Then bind each channel to its implementation inside the enum:

public enum PayChannel {
    WECHAT("wechat", amount -> System.out.println("使用微信支付 " + amount)),
    ALIPAY("alipay", amount -> System.out.println("使用支付宝支付 " + amount)),
    BANKCARD("bankcard", amount -> System.out.println("使用银行卡支付 " + amount)),
    PAYPAL("paypal", amount -> System.out.println("使用PayPal支付 " + amount)),
    APPLEPAY("applepay", amount -> System.out.println("使用Apple Pay支付 " + amount));

    private final String code;
    private final PayHandler handler;

    PayChannel(String code, PayHandler handler) {
        this.code = code;
        this.handler = handler;
    }

    public void pay(BigDecimal amount) {
        handler.pay(amount);
    }

    public static PayChannel fromCode(String code) {
        return Arrays.stream(values())
                .filter(c -> c.code.equals(code))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("不支持的支付渠道: " + code));
    }

    public String getCode() {
        return code;
    }
}

Usage is a single clean call:

String channelCode = "alipay";
BigDecimal amount = new BigDecimal("99.99");
PayChannel channel = PayChannel.fromCode(channelCode);
channel.pay(amount);
Advantages: logic is centralized, adding a new channel only requires a new enum constant.

Correct Approach 2: Enum References Spring Beans

When each channel needs a complex service, store the Spring bean name and retrieve it via ApplicationContext:

@Component
public enum PayChannelServiceLocator {
    WECHAT("wechat", "weChatPayService"),
    ALIPAY("alipay", "alipayPayService"),
    BANKCARD("bankcard", "bankCardPayService");

    private final String code;
    private final String beanName;

    PayChannelServiceLocator(String code, String beanName) {
        this.code = code;
        this.beanName = beanName;
    }

    public void pay(BigDecimal amount, ApplicationContext ctx) {
        PaymentService service = ctx.getBean(beanName, PaymentService.class);
        service.pay(amount);
    }

    public static PaymentService getService(String code, ApplicationContext ctx) {
        return Arrays.stream(values())
                .filter(c -> c.code.equals(code))
                .findFirst()
                .map(c -> ctx.getBean(c.beanName, PaymentService.class))
                .orElseThrow(() -> new IllegalArgumentException("不支持的渠道: " + code));
    }
}

Correct Approach 3: Map + @PostConstruct Injection

Inject all PaymentService implementations into a map keyed by channel code:

@Service
public class PayChannelStrategyService {
    private final Map<String, PaymentService> strategyMap = new HashMap<>();

    public PayChannelStrategyService(List<PaymentService> services) {
        services.forEach(s -> strategyMap.put(s.getSupportedChannel(), s));
    }

    public void pay(String channel, BigDecimal amount) {
        PaymentService service = strategyMap.get(channel);
        if (service == null) {
            throw new IllegalArgumentException("不支持的支付渠道: " + channel);
        }
        service.pay(amount);
    }
}

Each concrete service implements:

@Service
public class WeChatPayService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("微信支付: " + amount);
    }

    @Override
    public String getSupportedChannel() {
        return "wechat";
    }
}

The controller simply delegates:

@RestController
public class PayController {
    @Autowired
    private PayChannelStrategyService payService;

    @PostMapping("/pay")
    public String pay(@RequestParam String channel, @RequestParam BigDecimal amount) {
        payService.pay(channel, amount);
        return "支付成功";
    }
}

4. Best Practices and Pitfalls

Follow the Open/Closed principle.

Facilitate unit testing.

Add new channels by creating a new @Service implementation, without touching existing code.

Avoid any if‑else in the business layer.

Good code grows healthily; enums provide the fertile soil for that growth.

5. Conclusion

While if‑else is not inherently evil, when it expands into a tangled tree it should be refactored. Using enums with behavior, the strategy pattern, and Spring integration yields clean, extensible, and maintainable payment logic.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaStrategy Patternspringenumclean codedesign principles
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

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.