Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactor a switch case with java lambdas

Tags:

java

lambda

I am trying to re-factor a legacy code and in this case I have a huge block of switch case which decide which command to be executed

switch(operation)
case addition  : return add(int a, String b, String c);
case multiply  : return multiply(int a, int b);
case substract : return substract(int a, int b);

Approach 1 : using polymorphism

public interface Operation {
    void performOperation(int a, int b);
}

Then fill a map with the available implementations:

Map<Key, Operation> actions = new HashMap<>();
actions.add(addition, new addOperation());
actions.add(multiply, new multiplyOperation());
actions.add(substract, new substractOperation());

Then I can refer the map when I need to perform a operation.

The issues I have with this approach is that I am having to create a large number of classes / annonymous classes

Approach 2 : Using Enum

public enum MyKeyEnum {
    ADDITION {
        public void performOperation(int a, int b) {
            // Perform addition
        }
    },
    MULTIPLY {
        public void performOperation(int a, int b) {
            // Perform Multiplication
        }
    };

    public abstract void performOperation(int counter, String dataMain, String dataSub);
    }

This approach is actually better of the two but I saw another eaxmple in Java 8 and want use something like this

As all these are following a pattern I tried to use Functional Interface and Maps

final static Map<String, Supplier<IAction>> map = new HashMap<>();
static {
    map.put("add", Addition::new);
    map.put("multiply", Multiply::new);
}
public static void main(String[] args) throws Exception {
    Supplier<IAction> action = map.get("add");
    System.out.println(action.get().performAction(10,10));

    action = map.get("multiply");
    System.out.println(action.get().performAction(10,10));
}

But this again has the disadvantages of the first approach so wanted to see if I can use lambdas like I used Enum implementation There is a partial function implementation provided in Java 8 which I wanted to utilize Example :

BiFunction<Integer, Integer, Integer> minus = (x, y) -> x - y;
Function<Integer, Integer> subtractor = partial(minus, 10);
System.out.println(subtractor.apply(4)); // 6

as BiFunction is accepting only 2 parameters I created a Trifuction like

@FunctionalInterface
interface TriFunction<T, U, V, R> {
    R apply(T a, U b, V c);
}

public static <T, U, V, R> Function<V, R> partial(TriFunction<T, U, V, R> f, T x, U y) {
    return (z) -> f.apply(x, y, z);
}

This will resolve the issue to an extent but I am not able to figure out how I can add this to the map and dynamically pass values

Map<String, TriFunction<String, Integer, Integer, Operation>> map
= new HashMap<>();
like image 931
Sudheer Avatar asked Nov 09 '15 06:11

Sudheer


2 Answers

You are already there. If you've a method which has the same signature of your interface you can also pass it to your operation repository like:

Map<String, IntBinaryOperator> operations = new HashMap<>();
operations.put("add", Integer::sum);
operations.put("subtract", (a, b) -> a - b);
operations.put("multiply", (a, b) -> a * b);
//...
System.out.println(operations.get("multiply").applyAsInt(10, 20));
like image 182
Flown Avatar answered Sep 21 '22 03:09

Flown


Thx for your question, as i stumbled on the exact same problem. I was looking for a way to eliminate if if if or switch blocks in my code for some time.

I opted for the Map + Supplier approach, implemented within a factory, as in the following snippet:

public class OperatorFactory {
    private final String supportedOperatorsRegex = "\\+|\\*|-";
    private Map<String, Supplier<Operator>> supportedOperators = ImmutableMap.of(
        "+", Addition::new, "*", Multiplication::new, "-", Subtraction::new, "ID", Identity::new
    );

    public Operator parseToOperator(String input) {
        return supportedOperators.get(extractOperator(input)).get();
    }

    private String extractOperator(String input) {
        Matcher matcher = Pattern.compile(supportedOperatorsRegex).matcher(input);

        if (matcher.find()) return matcher.group();
        if (input.matches("\\d+")) return "ID";

        throw new UnsupportedOperationException("unsupported operation: " + input);
    }
}

I find this sollution closer to the Open / Close Principle (the O in SOLID principles):

  1. Now, all i need to do to extend the functionalities is to add another operator class that implements some Operator interface that i have defined elsewhere, add it in the supportedOperators map and in the SupportedOperatorsRegex without changing any of the business logic of the OperatorFactory class.
  2. Moreover, the client classes do not need to know what is happening in the OperatorFactory, and that is less coupling.
like image 45
avi.elkharrat Avatar answered Sep 24 '22 03:09

avi.elkharrat