Fundamentals 22 min read

Mastering Conditional Logic: 4 Design Patterns to Replace If‑Else Explosions

This article explores four elegant design patterns—Strategy, SPI, Chain of Responsibility, and Rule Engine—explaining their concepts, advantages, and practical Java code examples to help developers replace tangled if‑else statements with maintainable, extensible solutions.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Mastering Conditional Logic: 4 Design Patterns to Replace If‑Else Explosions

1 Strategy Pattern

01 Concept

Strategy (Strategy Pattern) is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing the algorithm to vary independently from the client.

It replaces complex if‑else logic by delegating the decision to concrete strategy classes.

02 Example

public interface Strategy {
    int doOperation(int num1, int num2);
}

Concrete strategies implement the interface:

public class OperationAdd implements Strategy {
    @Override
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}

public class OperationSubtract implements Strategy {
    @Override
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}

public class OperationMultiply implements Strategy {
    @Override
    public int doOperation(int num1, int num2) {
        return num1 * num2;
    }
}

The context holds a reference to a Strategy and delegates execution:

public class Context {
    private Strategy strategy;
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    public int executeStrategy(int num1, int num2) {
        return strategy.doOperation(num1, num2);
    }
}

Client code demonstrates switching strategies at runtime:

public static void main(String[] args) {
    Context context = new Context();
    context.setStrategy(new OperationAdd());
    System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
    context.setStrategy(new OperationSubtract());
    System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
    context.setStrategy(new OperationMultiply());
    System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}

Advantages

Perfectly supports the Open‑Closed Principle.

Encapsulates families of algorithms.

Eliminates massive conditional statements.

Disadvantages

Clients must be aware of all concrete strategy classes.

Can lead to many small classes; the Flyweight pattern may mitigate this.

2 SPI Mechanism

01 Concept

SPI (Service Provider Interface) is a service‑discovery mechanism that loads implementations of an interface from configuration files at runtime.

Implementations are listed by fully‑qualified class name in a file under META-INF/services, and ServiceLoader reads the file to instantiate the classes.

02 Java SPI – JDBC Driver

Before JDBC 4.0, developers manually loaded the driver with Class.forName("com.mysql.jdbc.Driver"). Since JDBC 4.0, the driver is discovered via SPI, eliminating explicit loading.

// Register driver (pre‑JDBC 4.0)
Class.forName("com.mysql.jdbc.Driver");
// Open connection
Connection conn = DriverManager.getConnection(url, username, password);

Internally, DriverManager uses ServiceLoader.load(Driver.class) to locate driver implementations defined in META-INF/services/java.sql.Driver.

The loading process involves:

Reading driver definitions from system properties.

Using SPI to obtain implementation class names.

Instantiating each implementation.

Selecting the driver that accepts the JDBC URL.

03 Dubbo SPI – On‑Demand Loading

Dubbo implements its own SPI (ExtensionLoader) to support on‑demand loading. Configuration files are placed under META-INF/dubbo as key‑value pairs, e.g.:

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

Example usage:

ExtensionLoader<Robot> loader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = loader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = loader.getExtension("bumblebee");
bumblebee.sayHello();

Dubbo SPI adds full decoupling, hot‑plug capability, and supports IOC/AOP features.

3 Chain of Responsibility Pattern

01 Concept

The Chain of Responsibility pattern creates a linked list of handler objects, each capable of processing a request or passing it to the next handler.

It is useful when multiple objects can handle the same request and the handling order matters.

02 Example – Composite Payment

Define a handler interface:

public interface PaymentHandler {
    boolean canProcess(String userId);
    PaymentResult processPayment(String userId, double amount);
}

Concrete handlers for corporate and personal accounts:

public class CorporatePaymentHandler implements PaymentHandler {
    private static final double MAX_CORPORATE_PAYMENT = 5000;
    @Override
    public boolean canProcess(String userId) {
        return userId.startsWith("emp_");
    }
    @Override
    public PaymentResult processPayment(String userId, double amount) {
        System.out.printf("Attempt corporate payment: user[%s], amount[%.2f]%n", userId, amount);
        if (amount <= MAX_CORPORATE_PAYMENT) {
            return new PaymentResult(true, amount, "Corporate payment succeeded");
        } else {
            return new PaymentResult(true, MAX_CORPORATE_PAYMENT,
                String.format("Corporate partial payment succeeded (%.2f), remaining %.2f", MAX_CORPORATE_PAYMENT, amount - MAX_CORPORATE_PAYMENT));
        }
    }
}

public class PersonalPaymentHandler implements PaymentHandler {
    private static final double MAX_PERSONAL_PAYMENT = 1000;
    @Override
    public boolean canProcess(String userId) {
        return true; // all users can use personal account
    }
    @Override
    public PaymentResult processPayment(String userId, double amount) {
        System.out.printf("Attempt personal payment: user[%s], amount[%.2f]%n", userId, amount);
        if (amount <= MAX_PERSONAL_PAYMENT) {
            return new PaymentResult(true, amount, "Personal payment succeeded");
        } else {
            return new PaymentResult(true, MAX_PERSONAL_PAYMENT,
                String.format("Personal partial payment succeeded (%.2f), remaining %.2f", MAX_PERSONAL_PAYMENT, amount - MAX_PERSONAL_PAYMENT));
        }
    }
}

Composite context that iterates over handlers:

public class CompositePaymentContext {
    private final List<PaymentHandler> handlers = new ArrayList<>();
    public void addHandler(PaymentHandler handler) { handlers.add(handler); }
    public Map<String, PaymentResult> executePayment(String userId, double totalAmount) {
        Map<String, PaymentResult> results = new LinkedHashMap<>();
        double remaining = totalAmount;
        for (PaymentHandler handler : handlers) {
            if (handler.canProcess(userId) && remaining > 0) {
                PaymentResult result = handler.processPayment(userId, remaining);
                results.put(handler.getClass().getSimpleName(), result);
                if (result.isSuccess()) {
                    remaining -= result.getPaidAmount();
                    if (remaining <= 0) break;
                }
            }
        }
        if (remaining > 0) {
            results.put("Remaining", new PaymentResult(false, remaining,
                String.format("Payment incomplete, remaining amount: %.2f", remaining)));
        }
        return results;
    }
}

Test class demonstrates three scenarios: corporate‑only payment, composite payment, and personal‑only payment.

4 Rule Engine

01 Concept

Rule engines separate business rules from code, allowing non‑technical users to configure rules that are stored as strings (e.g., in a database) and evaluated at runtime.

02 Example – AviatorScript

Rule script for a discount activity:

if (amount >= 1000) {
    return 200;
} elsif (amount >= 500) {
    return 100;
} else {
    return 0;
}

Utility method to evaluate the script:

public static BigDecimal getDiscount(BigDecimal amount, String rule) {
    Map<String, Object> env = new HashMap<>();
    env.put("amount", amount);
    Expression expression = AviatorEvaluator.compile(DigestUtils.md5Hex(rule.getBytes()), rule, true);
    Object result = expression.execute(env);
    if (result != null) {
        return new BigDecimal(String.valueOf(result));
    }
    return null;
}

Test execution:

String rule = "if (amount>=1000){
    return 200;
}elsif(amount>=500){
    return 100;
}else{
    return 0;
}";
BigDecimal discount = getDiscount(new BigDecimal("600"), rule);
System.out.println("discount:" + discount); // prints 100

Advantages

Business personnel can configure rules without developer involvement.

Reduces development and deployment cost.

Improves rule transparency and maintainability.

5 Summary

In everyday development, conditional‑explosion problems arise when many if/else branches are needed. Traditional nested if/else code becomes hard to read, maintain, and extend.

The article presented four design patterns—Strategy, SPI, Chain of Responsibility, and Rule Engine—that address this issue by encapsulating varying behavior, enabling runtime discovery, chaining handlers, and externalizing business rules.

Choosing the right pattern follows core design principles such as Single Responsibility, Open‑Closed, high cohesion & low coupling, and KISS, ultimately leading to maintainable, extensible, and well‑structured code.

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.

Chain of ResponsibilityDesign Patternsrule engineSoftware ArchitectureStrategy PatternSPI
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.