Java – How to avoid switch-case statements in Java

design-patternsenumsjavasolid-principlesswitch statement

I have an enum of TriggerType, where different triggers can be added

public enum TriggerType {
    meta,data,list,toggle
}

These trigger types are used inside different handlers (eg Component, Dashboard etc) to identify which trigger is triggered inside the handler through a switch-case, for eg Code snippet of ComponentHandler using trigger through switch-case is given below

@Override
public TriggerResultInterface executeTriggerJob(TriggerEventHelper triggerEventHelper) throws TriggerHandlerException {
    switch (triggerEventHelper.getTriggerName()) {
        case meta:
            return getMetaComponentConfig(triggerEventHelper);
        case data:
            return getComponentData(triggerEventHelper);
        default:
            LOGGER.debug(INVALID_TRIGGER_NAME_CONFIGURED);
            throw new TriggerHandlerException(INVALID_TRIGGER_NAME_CONFIGURED);
    }

}

Imagine If I want to add a new Trigger, I have to update the enum class which is unavoidable, at the same time I have to update each of my handler classes which that Trigger need to be used, Is this way of design with coding is good or is there any-other better solution that will enhance this code and follows SOLID principles along with a better design.

I would like to stress on saying that this question is not a duplicate of this. There is only one behavior needed in that situation for each type (eg: convertToMp3). But what my question refers to is my enum type (Trigger Type) is dependent on Handlers which it could possibly be used, so each Trigger Type enum's behavior or implementation will depend on the requirement of the handler it is being used.

Best Answer

One of the solution is to use polymorphism to handle triggers differently. For instance, you could declare the Trigger interface and have several implementations. In this case, when you need a new trigger type, you just implement this interface and don't touch the existing code:

public interface Trigger {
    TriggerResultInterface execute(TriggerEventHelper eventHelper);
}

public class MetaTrigger implements Trigger {
    @Override
    TriggerResultInterface execute(TriggerEventHelper eventHelper) {
        // do meta trigger work here
    }
}

public class DataTrigger implements Trigger {
    @Override
    TriggerResultInterface execute(TriggerEventHelper eventHelper) {
        // do data trigger work here
    }
}

// ...

public TriggerResultInterface executeTriggerJob(TriggerEventHelper eventHelper) {
    eventHelper.getTrigger().execute(eventHelper);
}

In this case it will be impossible to add a new trigger type and not implement its behaviour.

If you need a default implementation, you can use a base class instead of the interface (in Java 8 you can add a default implementation right into the interface).