Refactoring Long if...else Chains in Java Backend Services
This article explains why lengthy if...else statements in a Java payment service violate design principles and demonstrates several backend‑focused refactoring techniques—annotation binding, dynamic bean naming, template methods, strategy‑factory, and chain‑of‑responsibility—to replace the conditional logic with clean, extensible patterns.
Introduction
During a recent code‑refactor the author discovered a very long and smelly if...else chain in a payment service, which clearly breaks the Open‑Closed and Single‑Responsibility principles.
Problem Example
The original PayService class decides which payment implementation to invoke based on a code string, using nested if / else if statements for AliaPay, WeixinPay, JingDongPay, etc.
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("找不到支付方式"); }
}
}Adding new payment methods would require constantly modifying this method.
Solution 1 – Annotation Binding
Define a custom @PayCode annotation and annotate each payment implementation with a unique value . At application start, scan beans with this annotation, build a Map<String, IPay> , and retrieve the correct implementation directly.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode { String value(); String name(); }
@PayCode(value = "alia", name = "支付宝支付")
@Service
public class AliaPay implements IPay { /* ... */ }
@PayCode(value = "weixin", name = "微信支付")
@Service
public class WeixinPay implements IPay { /* ... */ }
@PayCode(value = "jingdong", name = "京东支付")
@Service
public class JingDongPay implements IPay { /* ... */ }
@Service
public class PayService2 implements ApplicationListener
{
private static Map
payMap;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext ctx = event.getApplicationContext();
Map
beans = ctx.getBeansWithAnnotation(PayCode.class);
payMap = new HashMap<>();
beans.forEach((k, v) -> {
String biz = v.getClass().getAnnotation(PayCode.class).value();
payMap.put(biz, (IPay) v);
});
}
public void pay(String code) { payMap.get(code).pay(); }
}This eliminates all conditional branches; adding a new payment class only requires the annotation.
Solution 2 – Dynamic Bean Name
If the code has business meaning, construct the bean name by appending a suffix (e.g., Pay ) and retrieve it from the Spring context.
@Service
public class PayService3 implements ApplicationContextAware {
private ApplicationContext ctx;
private static final String SUFFIX = "Pay";
@Override
public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; }
public void toPay(String code) { ((IPay) ctx.getBean(code + SUFFIX)).pay(); }
}The bean names must follow the code+"Pay" convention.
Solution 3 – Template Method
Define an IPay interface with a support(String code) method. Each implementation returns true only for the codes it handles. A central service iterates over all IPay beans and invokes the first supporting one.
public interface IPay { boolean support(String code); void pay(); }
@Service
public class AliaPay implements IPay {
@Override public boolean support(String code) { return "alia".equals(code); }
@Override public void pay() { System.out.println("===发起支付宝支付==="); }
}
@Service
public class PayHandlerChain 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()); }
public void toPay(String code) { for (IPay p : payList) if (p.support(code)) { p.pay(); return; } }
}Solution 4 – Strategy + Factory
Each payment implementation registers itself into a static PayStrategyFactory map during @PostConstruct . The service then obtains the correct strategy by code.
@Service
public class AliaPay implements IPay {
@PostConstruct public void init() { PayStrategyFactory.register("aliaPay", this); }
@Override public void pay() { System.out.println("===发起支付宝支付==="); }
}
public class PayStrategyFactory {
private static final Map
REGISTRY = new HashMap<>();
public static void register(String code, IPay impl) { REGISTRY.put(code, impl); }
public static IPay get(String code) { return REGISTRY.get(code); }
}
@Service
public class PayService3 {
public void toPay(String code) { PayStrategyFactory.get(code).pay(); }
}Solution 5 – Chain of Responsibility
Implement an abstract PayHandler with a next reference. Each concrete handler processes its own code or forwards the request down the chain. A PayHandlerChain bean assembles the chain at startup.
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); }
}
@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).setNext(handlers.get(i + 1));
header = handlers.get(0);
}
public void handlePay(String code) { header.pay(code); }
}Other Minor Refactorings
Replace repetitive if checks with enums, use Java 8 streams for collection filtering, and apply the ternary operator for simple branches. Spring’s Assert class can also simplify parameter validation.
Conclusion
Long if...else chains are a classic code smell. By leveraging annotations, Spring’s bean factory, template methods, strategy‑factory, or chain‑of‑responsibility patterns, developers can make the code open for extension, closed for modification, and much easier to maintain.
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.