How to Refactor Smelly if…else Chains in Java: 5 Clean Design‑Pattern Solutions

This article examines the problems of long if…else statements in Java payment services, explains why they violate the Open‑Closed and Single Responsibility principles, and presents five practical refactoring techniques—including annotations, dynamic bean names, template methods, strategy‑factory, and chain‑of‑responsibility patterns—to eliminate the conditional logic.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
How to Refactor Smelly if…else Chains in Java: 5 Clean Design‑Pattern Solutions

Preface

Recently while refactoring code I discovered many smelly patterns; this article focuses on how to refactor long and ugly if…else chains.

Before introducing better approaches, let’s review the problematic if…else code.

1. Long and Smelly if…else

Here is the original 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("找不到支付方式");
        }
    }
}

The PayService.toPay method decides which payment implementation to invoke based on a code string. Adding a new payment method requires modifying this method, which violates the Open‑Closed Principle and the Single Responsibility Principle.

Open‑Closed Principle: software should be open for extension but closed for modification.
Single Responsibility Principle: a class should have only one reason to change.

2. Strategies to Eliminate if…else

2.1 Use Annotations

Define a custom annotation and annotate each payment class with its code. At startup collect all beans with the annotation into a map and invoke the appropriate implementation directly.

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

Apply the annotation to the payment classes:

@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("===发起京东支付===");
    }
}

Collect the beans and build a map:

@Service
public class PayService2 implements ApplicationListener<ContextRefreshedEvent> {
    private static Map<String, IPay> payMap = null;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext ctx = event.getApplicationContext();
        Map<String, Object> beans = ctx.getBeansWithAnnotation(PayCode.class);
        if (beans != null) {
            payMap = new HashMap<>();
            beans.forEach((k, v) -> {
                String bizType = v.getClass().getAnnotation(PayCode.class).value();
                payMap.put(bizType, (IPay) v);
            });
        }
    }

    public void pay(String code) {
        payMap.get(code).pay();
    }
}

2.2 Dynamic Bean Name Construction

When the code has business meaning, construct the bean name by appending a suffix and retrieve the bean from the context.

@Service
public class PayService3 implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    private static final String SUFFIX = "Pay";

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.applicationContext = ctx;
    }

    public void toPay(String payCode) {
        ((IPay) applicationContext.getBean(getBeanName(payCode))).pay();
    }

    public String getBeanName(String payCode) {
        return payCode + SUFFIX;
    }
}

2.3 Template‑Method Style with a support Method

Define a support method in the payment interface and let each implementation decide whether it can handle the code.

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 PayService4 implements ApplicationContextAware, InitializingBean {
    private ApplicationContext applicationContext;
    private List<IPay> payList = null;

    @Override
    public void afterPropertiesSet() throws Exception {
        if (payList == null) {
            payList = new ArrayList<>();
            Map<String, IPay> beans = applicationContext.getBeansOfType(IPay.class);
            beans.forEach((k, v) -> payList.add(v));
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.applicationContext = ctx;
    }

    public void toPay(String code) {
        for (IPay iPay : payList) {
            if (iPay.support(code)) {
                iPay.pay();
                break;
            }
        }
    }
}

2.4 Strategy + Factory Pattern

Each payment implementation registers itself in a global factory; the service looks up the implementation 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("===发起支付宝支付===");
    }
}

public class PayStrategyFactory {
    private static final Map<String, IPay> PAY_REGISTERS = new HashMap<>();
    public static void register(String code, IPay iPay) {
        if (code != null && !"".equals(code)) {
            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();
    }
}

2.5 Chain‑of‑Responsibility

Build a chain of handler objects, each responsible for one payment type.

public abstract class PayHandler {
    protected PayHandler next;
    public void setNext(PayHandler next) { this.next = 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 if (next != null) {
            next.pay(code);
        }
    }
}

@Service
public class PayHandlerChain implements ApplicationContextAware, InitializingBean {
    private ApplicationContext applicationContext;
    private PayHandler header;

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.applicationContext = ctx;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, PayHandler> beans = applicationContext.getBeansOfType(PayHandler.class);
        if (beans == null || beans.isEmpty()) return;
        List<PayHandler> handlers = new ArrayList<>(beans.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);
    }
}

3. Other Ways to Remove if…else

3.1 Use an Enum Instead of Multiple Conditions

public enum MessageEnum {
    SUCCESS(1, "成功"),
    FAIL(-1, "失败"),
    TIME_OUT(-2, "网络超时"),
    PARAM_ERROR(-3, "参数错误");

    private final int code;
    private final String message;

    MessageEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public int getCode() { return code; }
    public String getMessage() { return message; }
    public static MessageEnum getMessageEnum(int code) {
        return Arrays.stream(values())
                     .filter(e -> e.code == code)
                     .findFirst()
                     .orElse(null);
    }
}

public String getMessage(int code) {
    MessageEnum e = MessageEnum.getMessageEnum(code);
    return e != null ? e.getMessage() : "code错误";
}

3.2 Ternary Operator for Simple Conditions

public String getMessage2(int code) {
    return code == 1 ? "成功" : "失败";
}

3.3 Spring Assert for Parameter Validation

public void save2(Integer code, String name) {
    Assert.notNull(code, "code不能为空");
    Assert.notNull(name, "name不能为空");
    System.out.println("doSave");
}

These examples demonstrate how to replace verbose if…else logic with more maintainable, extensible patterns.

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.

Javaspringrefactoringif-elseDesignPatternsOpenClosedPrincipleStrategyPattern
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.