3

I have enum class with values which are suppose to grow by time and I want users who add new enum values also provide the impementation somewhere. But I am not sure how to force them to provide impementation as impementation will be in some other class. For e.g.

public enum DayType {
    SUNDAY,
    MONDAY;
}

Referred in a class

class X{
DateType dateType;
..
}

And used in some other class

if (x.getDateType().equals(DayType.SUNDAY)) {
...
}else if(x.getDateType().equals(DayType.MONDAY)){
..
}

So if someone adds DateType then he should be forced to add impementation in above if-else logic as well. Preferably by adding functional interfaces if possible?

I can not force impementation in enum class as the impementation has spring dependencies.

3
  • I don't really think you can (or should) force them programmatically. Is it possible to just set it up in a clean way (with a default path) so that you can throw a fitting exception there hinting the developer that they need to do the implementation? Commented Feb 4, 2019 at 8:59
  • This really boils down to programmer diligence. You must make sure that a "default" or "unimplemented" branch is always offered when handling actions for your enum values. If the "unimplemented" case is a programmer error, then throw a RuntimeException to express this fact. Commented Feb 4, 2019 at 9:11
  • I appreciate the quick comeback! Commented Feb 9, 2019 at 18:51

8 Answers 8

5

The real answer is: don't do this.

If you have some "type", and you know that this "type" will see new incarnations over time, that need different behavior, then the answer is to use an abstract class and polymorphism.

It doesn't matter if you use if/else chains, or switch statements: the idea that you have something like:

if (someObject.getSomething() == whatever) { then do this } else { that }

is bad practice!

If at all, you should use such a switch statement within a factory to return different subclass instances, to then call "common" methods on such objects.

Your current approach is A) externalizing knowledge about internal state and B) also enforcing dependencies in multiple ways.

This is how spaghetti code starts! Better step back and think about more OOP ways of solving this!

Sign up to request clarification or add additional context in comments.

4 Comments

I agree with this post, however, I imagine think the problem still arises for a factory as well. Now its a matter of "How do I handle adding factory methods in all appropriate places when a new 'input' type is dreamed up?". Regardless of the abstraction, I think an appropriate answer is to program defensively by throwing an exception which informs the user of the missing use-case.
@flakes The point of the factory is to hide that ugly switch as much as possible. Sometimes you can't avoid it completely, but the idea would be that the "essential" parts are in different classes extending the base class, and then you have exactly one factory method that maybe does such kind of switch statement, to return instances of objects that you can call methods on.
Actually, I think I resolved this problem with the factory or at least I offered a reasonable option for factory without if-else clause. The factory stores implementations in the map which means O(1) access. See my answer to this question and references to the articles about MgntUtils open source library
also I agree with and like your answer - upvoting it
2

Though I doubt this could be restricted at compile time. A possible cleaner way to implement something similar would be using switch here :

switch (x.getDateType()) {
    case MONDAY: // do something
        break;
    case SUNDAY: // do something
        break;
    default:
        throw new CustomException("No implementations yet for :" + x.getDateType()); 
}

2 Comments

Your operations engineer will greatly appreciate it if you also add the name of the unhandled type to the CustomException message ;)
This is not an object-oriented solution. More object-oriented is to use polymorphism for example by applying the strategy pattern.
2

An alternative to nullpointer's option is to put that logic in the enum itself, assuming that this makes sense in your design (if that responsibility can lie with the day type enum).

public enum DayType {
    SUNDAY {
        @Override
        void processSomeAction(Object input) {
            super.processSomeAction(input);
            // process action with SUNDAY-specific logic
        }
    },

    MONDAY;

    //can be made abstract if there's no default implementation
    void processSomeAction(Object input) {
        // process action with default logic
    }
}

This will ensure that the need to provide day-specific implementation is obvious to developers who make changes to DayType.

And the caller will just need:

x.getDateType().processSomeAction(inputIfApplicable);

1 Comment

This is also a good solution but quickly gets unwieldy when there are many types and the logic is substantial. Works perfectly for relatively simple cases.
1

You can create One interface which will implements by your Enum class. In that case all the enum have to implement method in interface.

public enum DateType implements DateTypeInterface {

    SUNDAY {
        @Override
        public void checkCondition() {
            System.out.println("Implement Sunday logic");
        }
    },
    MONDAY {
        @Override
        public void checkCondition() {
            System.out.println("Implement Monday logic.");
        }
    }
}


public interface DateTypeInterface {

     public void checkCondition();
}

Comments

1

I don't think you have an option at compile time to enforce your policy. What I would suggest is to create an interface (something like DateTypeAction) with a single method (say action()) and then a Factory that produces concrete implementations based on a value of your DateType (Something like DateTypeActionFactory.getDateTypeAction(DateType dateType)). At your initialization phase you can run through all the values of your DateType (DateType.values()) and check that there is non-null implementation of DateTypeAction in your DateTypeActionFactory for each value. If you find a missing implementation for one or more values throw an exception with explicit error message telling that an implementation(s) is/are missing and fail the start up of your app. This seems a reasonable pattern.

BTW if you go with this pattern, I have a recommendation: I wrote an open Source java library called MgntUtils that provides a simple framework (very well suited for use with Spring) that has a self-populating factory pattern. I.e. you can create an interface and a factory extending library provided parent classes and then each implementation of your interface will automatically be inserted into your factory with pre-defined name. I used it many times and found it very convinient. Here is the link to the article describing the entire library: MgntUtils Open Source Java library with stack trace filtering, Silent String parsing, Unicode converter and Version comparison. Look for paragraph

Lifecycle management (Self-instantiating factories)

for short explanation of the feature. Here you can read about the entire idea for the feature: Non-intrusive access to "Orphaned" Beans in Spring framework. Both articles explain where to get library as well, but here are the direct links: Maven Central Repository and Github. The library comes with well written javadoc

1 Comment

Nice answer ;-)
0

An enum was meant to hold compile time (static) final values so dynamically adding more values is impossible (Just to put it out there). As to the other part of your question, as @nullpointer pointed out, it is best to use switch clause. In addition to improved code clarity, the compiler warns if there are enum values that do not have case statements declared for them (that is, given you omit default)

Comments

0

I'm also not sure if designing enum for extension by user is a good idea. It's static, as @Garikai stated. This will lead to code smell.

If you would like to have easy option to extend mapping from enum to function I would propose Map<DateType, Function> map = new EnumMap<DateType, Function>(DateType.class); and then use getOrDefault to ensure that you will get any output.

Comments

0

This answer expands on some of the other answers here promoting use of polymorphism.

What I usually try to do to avoid having an every-growing switch/conditional somewhere, is to add an identifying method to the interface:

public enum Day {
    SUNDAY, MONDAY
}

public interface DayProcessor {

    Day day();

    // I don't know what Days are supposed to do exactly
    Object process(Object input); 
}

Then I create a factory component with all the available DayProcessor implementations wired in by Spring:

@Component
public class DayProcessorFactory {

    @Autowired
    private List<DayProcessor> dayProcessors;

    public DayProcessor create(Day day) {
        for (DayProcessor dayProcessor: dayProcessors) {
            if (dayProcessor.day().equals(day)) {
                return dayProcessor;
            }
        }
        // DayNotFoundException extends RuntimeException
        throw new DayNotFoundException(String.format("No DayProcessor found for day %s", day));
    }
}

This is a form of the Strategy Pattern.

An alternative way to list all the days is by enumerating them (but then you'd have to update the list when a new day gets created):

private DayProcessor[] dayProcessors = new DayProcessor[] {new MondayProcessor(), new SundayProcessor()};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.