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.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
user1298426
  • 3,467
  • 15
  • 50
  • 96
  • 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? – Ben Feb 04 '19 at 08: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. – flakes Feb 04 '19 at 09:11
  • I appreciate the quick comeback! – GhostCat Feb 09 '19 at 18:51

8 Answers8

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!

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 1
    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 Feb 04 '19 at 09:19
  • @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. – GhostCat Feb 04 '19 at 09:28
  • 1
    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 – Michael Gantman Feb 04 '19 at 09:29
  • 1
    also I agree with and like your answer - upvoting it – Michael Gantman Feb 04 '19 at 09:36
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()); 
}
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 3
    Your operations engineer will greatly appreciate it if you also add the name of the unhandled type to the `CustomException` message ;) – flakes Feb 04 '19 at 09:05
  • This is not an object-oriented solution. More object-oriented is to use polymorphism for example by applying the strategy pattern. – Adriaan Koster Feb 04 '19 at 09:28
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);
ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • 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. – Adriaan Koster Feb 04 '19 at 09:59
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();
}
Ravi Sapariya
  • 395
  • 2
  • 11
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

Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
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)

Garikai
  • 405
  • 2
  • 15
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.

MicD
  • 197
  • 2
  • 11
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()};
Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60