Backend Development 15 min read

Refactoring Long if...else Chains in Java Using Design Patterns and Spring

This article explains why lengthy if...else statements in Java payment services violate the Open‑Closed and Single‑Responsibility principles and demonstrates several refactoring techniques—including annotations, dynamic bean naming, template methods, strategy‑factory, and chain‑of‑responsibility patterns—using Spring to eliminate the conditional logic.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Refactoring Long if...else Chains in Java Using Design Patterns and Spring

In many Java projects, payment services are implemented with a long if...else chain that selects a concrete IPay implementation based on a string code. This approach breaks the Open‑Closed principle (the code must be modified for every new payment method) and the Single‑Responsibility principle (the method does more than just delegate).

The article first shows the problematic code:

public interface IPay { void pay(); }

@Service
public class AliaPay implements IPay { @Override public void pay() { System.out.println("===发起支付宝支付==="); } }

@Service
public class WeixinPay implements IPay { @Override public void pay() { System.out.println("===发起微信支付==="); } }

@Service
public class JingDongPay implements IPay { @Override public void pay() { System.out.println("===发起京东支付==="); } }

@Service
public class PayService {
    @Autowired private AliaPay aliaPay;
    @Autowired private WeixinPay weixinPay;
    @Autowired private JingDongPay jingDongPay;
    public void toPay(String code) {
        if ("alia".equals(code)) { aliaPay.pay(); }
        else if ("weixin".equals(code)) { weixinPay.pay(); }
        else if ("jingdong".equals(code)) { jingDongPay.pay(); }
        else { System.out.println("找不到支付方式"); }
    }
}

When new payment channels (e.g., Baidu, Meituan, UnionPay) are added, the toPay method must be edited, leading to code that is hard to maintain.

1. Using Annotations

Define a custom annotation to bind a code to a payment bean, then collect all beans with that annotation at startup:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode { String value(); String name(); }

Apply it to each payment class:

@PayCode(value = "alia", name = "支付宝支付")
@Service
public class AliaPay implements IPay { @Override public void pay() { System.out.println("===发起支付宝支付==="); } }

@PayCode(value = "weixin", name = "微信支付")
@Service
public class WeixinPay implements IPay { @Override public void pay() { System.out.println("===发起微信支付==="); } }

@PayCode(value = "jingdong", name = "京东支付")
@Service
public class JingDongPay implements IPay { @Override public void pay() { System.out.println("===发起京东支付==="); } }

A listener builds a Map<String, IPay> where the key is the annotation value, allowing payMap.get(code).pay() without any if...else .

2. Dynamic Bean Name Concatenation

If the code has business meaning, you can construct the bean name at runtime:

@Service
public class PayService3 implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    private static final String SUFFIX = "Pay";
    @Override public void setApplicationContext(ApplicationContext ctx) { this.applicationContext = ctx; }
    public void toPay(String payCode) { ((IPay) applicationContext.getBean(getBeanName(payCode))).pay(); }
    private String getBeanName(String payCode) { return payCode + SUFFIX; }
}

Bean names such as aliaPay , weixinPay must match the code prefix.

3. Template Method (Strategy) Pattern

Each IPay implementation provides a support(String code) method; the service iterates over a list of beans and invokes the first that supports the code:

public interface IPay { boolean support(String code); void pay(); }

@Service
public class AliaPay implements IPay { @Override public boolean support(String c) { return "alia".equals(c); } @Override public void pay() { System.out.println("===发起支付宝支付==="); } }
// similar for WeixinPay and JingDongPay

@Service
public class PayService4 implements ApplicationContextAware, InitializingBean {
    private ApplicationContext ctx;
    private List
payList;
    @Override public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }
    @Override public void afterPropertiesSet() {
        payList = new ArrayList<>();
        ctx.getBeansOfType(IPay.class).values().forEach(payList::add);
    }
    public void toPay(String code) { for (IPay p : payList) { if (p.support(code)) { p.pay(); break; } } }
}

4. Strategy + Factory Pattern

Register each IPay instance in a global factory during @PostConstruct and retrieve it by code:

public interface IPay { void pay(); }

@Service
public class AliaPay implements IPay {
    @PostConstruct public void init() { PayStrategyFactory.register("aliaPay", this); }
    @Override public void pay() { System.out.println("===发起支付宝支付==="); }
}
// similar for WeixinPay and JingDongPay

public class PayStrategyFactory {
    private static final Map
PAY_REGISTERS = new HashMap<>();
    public static void register(String code, IPay iPay) { if (code != null && !code.isEmpty()) PAY_REGISTERS.put(code, iPay); }
    public static IPay get(String code) { return PAY_REGISTERS.get(code); }
}

@Service
public class PayService3 { public void toPay(String code) { PayStrategyFactory.get(code).pay(); } }

5. Chain‑of‑Responsibility

Build a handler chain where each handler either processes the code or forwards to the next:

public abstract class PayHandler {
    protected PayHandler next;
    public abstract void pay(String code);
}

@Service
public class AliaPayHandler extends PayHandler {
    @Override public void pay(String code) { if ("alia".equals(code)) System.out.println("===发起支付宝支付==="); else next.pay(code); }
}
// WeixinPayHandler and JingDongPayHandler similar

@Service
public class PayHandlerChain implements ApplicationContextAware, InitializingBean {
    private ApplicationContext ctx;
    private PayHandler header;
    @Override public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }
    @Override public void afterPropertiesSet() {
        List
handlers = new ArrayList<>(ctx.getBeansOfType(PayHandler.class).values());
        for (int i = 0; i < handlers.size() - 1; i++) handlers.get(i).next = handlers.get(i + 1);
        header = handlers.get(0);
    }
    public void handlePay(String code) { header.pay(code); }
}

6. Other Typical Cases

For simple numeric‑to‑string mapping, replace a long if...else chain with an enum that holds the code and message, and look it up via Arrays.stream(...).filter(...) . For trivial binary decisions, use the ternary operator. In Spring, use Assert.notNull(...) to validate method arguments instead of nested if blocks.

These patterns dramatically reduce conditional complexity, improve extensibility, and keep the codebase aligned with SOLID principles.

Finally, the author asks readers to like, share, and follow the public account for more such technical insights.

design patternsJavaStrategy PatternSpringrefactoringif-else
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.