Avoiding Switch and if-Else using Map and Java Reflection

  1. Using Map to avoid Switch

Here's an exercise where you can use Java Reflection to avoid using switch cases and refactor the code to be more maintainable:

Problem: Handle Different Types of Operations Without Switch Statements

You are given an interface Operation with multiple implementations. Each implementation of Operation performs a different task, such as addition, subtraction, etc. Instead of using a switch statement to determine which operation to perform based on the type of the operation, use Reflection to dynamically call the appropriate method based on the operation type.

Exercise Steps:

Define the Operation interface:

public interface Operation {
    void execute();
}        

Implement different operation classes:

public class AddOperation implements Operation {
    @Override
    public void execute() {
        System.out.println("Performing Addition");
    }
}

public class SubtractOperation implements Operation {
    @Override
    public void execute() {
        System.out.println("Performing Subtraction");
    }
}

public class MultiplyOperation implements Operation {
    @Override
    public void execute() {
        System.out.println("Performing Multiplication");
    }
}
        

Create a class OperationExecutor that uses Reflection:

Instead of using a switch statement to determine which operation to execute, you can use Reflection to dynamically invoke the execute method based on the operation type.

import java.lang.reflect.Method;

public class OperationExecutor {
    public void executeOperation(String operationType) {
        try {
            // Dynamically determine the class based on operation type
            String className = "com.example." + operationType + "Operation";  // Assuming operation classes are named this way
            Class<?> operationClass = Class.forName(className);
            Object operationInstance = operationClass.getDeclaredConstructor().newInstance();
            
            // Invoke the execute method using Reflection
            Method executeMethod = operationClass.getMethod("execute");
            executeMethod.invoke(operationInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
        

Test the solution:

public class Main {
    public static void main(String[] args) {
        OperationExecutor executor = new OperationExecutor();
        
        executor.executeOperation("Add");  // Performing Addition
        executor.executeOperation("Subtract");  // Performing Subtraction
        executor.executeOperation("Multiply");  // Performing Multiplication
    }
}         

Explanation:

  • No Switch Case: In this solution, you avoid using a switch statement to determine which operation to execute.
  • Reflection: We use Class.forName() to dynamically load the class based on the operationType string. We then create an instance of the class using reflection and invoke the execute() method using Method.invoke().
  • Scalability: If new operations are added in the future, you just need to create new classes that implement Operation without needing to modify the OperationExecutor class, making the code more maintainable and scalable.

Benefits:

  • Extensibility: Adding new operations does not require modifying the OperationExecutor class.
  • Avoiding Hardcoding: You avoid hardcoding the specific operations in a switch statement.



Using Map to avoid Switch

Here's another exercise where you can avoid using a switch statement by leveraging a Map. This time, let's implement a system where we dynamically map operations to their corresponding functions using a Map and avoid using conditional statements like switch.

Problem: Map-Based Operation Dispatcher

You are tasked with creating a system that performs different arithmetic operations (addition, subtraction, multiplication, division) based on the input operation type, without using a switch statement. Use a Map to dynamically associate operation types with their corresponding implementation.

Exercise Steps:

Define the Operation interface:

public interface Operation {
    double apply(double a, double b);
} 
        

Implement concrete operation classes:

public class AddOperation implements Operation {
    @Override
    public double apply(double a, double b) {
        return a + b;
    }
}

public class SubtractOperation implements Operation {
    @Override
    public double apply(double a, double b) {
        return a - b;
    }
}

public class MultiplyOperation implements Operation {
    @Override
    public double apply(double a, double b) {
        return a * b;
    }
}

public class DivideOperation implements Operation {
    @Override
    public double apply(double a, double b) {
        if (b == 0) throw new IllegalArgumentException("Cannot divide by zero");
        return a / b;
    }
}
        

Create the OperationExecutor class with a Map to avoid the switch:

import java.util.HashMap;
import java.util.Map;

public class OperationExecutor {
    private final Map<String, Operation> operationMap;

    public OperationExecutor() {
        operationMap = new HashMap<>();
        operationMap.put("add", new AddOperation());
        operationMap.put("subtract", new SubtractOperation());
        operationMap.put("multiply", new MultiplyOperation());
        operationMap.put("divide", new DivideOperation());
    }

    public double executeOperation(String operationType, double a, double b) {
        // Retrieve the operation dynamically using the map
        Operation operation = operationMap.get(operationType.toLowerCase());
        if (operation == null) {
            throw new IllegalArgumentException("Unknown operation: " + operationType);
        }
        return operation.apply(a, b);
    }
}
        

Test the solution:

public class Main {
    public static void main(String[] args) {
        OperationExecutor executor = new OperationExecutor();

        double resultAdd = executor.executeOperation("add", 5, 3);
        System.out.println("Addition: " + resultAdd);  // Output: 8.0

        double resultSubtract = executor.executeOperation("subtract", 5, 3);
        System.out.println("Subtraction: " + resultSubtract);  // Output: 2.0

        double resultMultiply = executor.executeOperation("multiply", 5, 3);
        System.out.println("Multiplication: " + resultMultiply);  // Output: 15.0

        double resultDivide = executor.executeOperation("divide", 6, 3);
        System.out.println("Division: " + resultDivide);  // Output: 2.0
    }
}
        

Explanation:

  • No Switch Case: Instead of using a switch statement, we use a Map to store the mapping between the operation type (as a string, like "add", "subtract", etc.) and the corresponding operation implementation.
  • Map-Based Dispatch: The operation type (e.g., "add", "subtract") is used as a key to retrieve the appropriate Operation object from the Map. Then, we invoke the apply() method on the retrieved operation.
  • Extensibility: If you need to add new operations (e.g., modulus, power), you can simply create a new class implementing Operation and add it to the Map without changing any existing logic in the OperationExecutor class.

Benefits:

  • No Switch or If-Else: The Map serves as a cleaner, more maintainable alternative to the switch statement.
  • Easy to Extend: Adding new operations is easy and doesn't require touching the OperationExecutor logic—just add a new entry to the map.
  • Dynamic Dispatch: Operations are dispatched dynamically based on runtime input, making the system more flexible and decoupled.

To view or add a comment, sign in

More articles by Dr. BADR EL KHALYLY

Insights from the community

Others also viewed

Explore topics