Using enums in an object-orientated way when they apply to many classes

143 Views Asked by At

So, I here was my previous code:

public enum eDay {
eMON,
eTUE,
eWED
}

eDay day = eMON;

switch( day ) {
case eMON:
//load class 1
break;

case eTUE:
//load class 2
break;

case eWED:
//load class 3
break;
}

Reading around, the OO way to do things is to have an enum that overrides a method rather than using a switch statement, such as:

enum eDay {
    eMON {
        @Override
        public void loadClass() {
            //load class 1
        }
    },
    eTUE {
        @Override
        public void loadClass() {
            //load class 2
        }
    };
    public abstract void loadClass();
}

void aMethodSomewhere(final eDay e) {
    e.loadClass();
}

While the concept of using polymorphism makes sense to me, rather than using a switch statement, what happens in the situation where the enum is used in different classes to do different things? For example, different classes behave differently according to a limited set of options, defined in the enum ( so not always performing loadClass() ).

Should you define different methods according to the different classes? To my mind, that would increase object coupling greatly.

I would really like to do OO properly, so good advice is much appreciated.

Many thanks.

3

There are 3 best solutions below

1
On BEST ANSWER

In general, you want to use polymorphism to avoid if() blocks and for behavior reuse, you should favor composition.

In your case, I understand that there is some dynamic behavior involved in your design and, therefore, the use of enums could not be advisable.

Enums have a lot of advantages: they're final, static and every instance is a singleton by default. Whenever I need a static singleton that is up when the JVM starts, I favor enums over classes.

In this case, if you have some dynamic behavior going on, you could write a class with some static final properties that are an instance of the same class. With proper visibility modifiers for constructors or factory methods, the external API of the class could be the same as with the enum.

In order to inject the dynamic behavior, you could use Strategy design pattern or even inject a Function (if you're using Java8) as a parameter in the class' constructor.

public static class eDays {
  public static final eDay eMON = new eDay(i -> i + 1);
  public static final eDay eTUE = new eDay(i -> i + 2);

  public static class eDay {
    private final Function<Integer, Integer> loadClassStrategy;

    public eDay(Function<Integer, Integer> loadClassStrategy) {
      this.loadClassStrategy = loadClassStrategy;
    }

    public int loadClass(int i) {
      return loadClassStrategy.apply(i);
    }
  }
}

void aMethodSomewhere(final eDays.eDay e) {
  e.loadClass(1);
}

// or even...

void aMethodSomewhere() {
  eDay eMON = new eDay(i -> i + 1);
  eMON.loadClass(1);
}

If your behavior is not dynamic, you could continue using enums, but you could inject the behavior in their constructor to improve readability:

public enum eDay {
  eMON(i->i+1),
  eTUE(i->i+2);

  private final Function<Integer, Integer> loadClassStrategy;

  eDay(Function<Integer, Integer> loadClassStrategy) {
    this.loadClassStrategy = loadClassStrategy;
  }

  public int loadClass(int i) {
    return loadClassStrategy.apply(i);
  }
}

void aMethodSomewhere(eDay e) {
  e.loadClass(1);
}
3
On

Enum in Java is used for the non-changeable and defined constants.
It seems to me that you would like to have differently behaived classes based on some conditions. If so the strategy patter would be more suitable for this task.

For more info click here.

19
On

I'd advise against putting this kind of behavior in enums; that's really not what they are designed for*. Instead, it might be better to maintain a map of enums and handlers.

Map<Eday, Loader> enumHandlerMap = new EnumMap<>();

Loader should be an interface (basically the stuff you had in your enum):

public interface Loader {
    void loadClass();
}

Then you can initialize your your handler map like so:

enumHanderMap.put(EDay.MON, new Loader() {
    @Override
    public void loadClass() {
        ...
    }
});

And you can run a handler like so:

enumHandler.get(eDayEnum).loadClass();

*To clarify, the semantic conveyed by enums is that you are dealing with a static, predefined set of values. I think an acceptable form of logic to include within enums is the kind that provides additional information about each of the values. The Planet enum is a good example because it provides additional information related to each enum. There is no strict rule to define what is "too much" logic. But if you find yourself implementing a method on an enum that talks to a database, or makes an HTTP connection, I would argue that you're doing too much. In general, the logic you implement inside the enum shouldn't concern itself with other parts of your model or business logic; it shouldn't need access to additional entities from your model, or other services. If you can convey additional information and behavior in the context of the enum itself, then you might be alright. But if you are using the enum to implement complex behavior that involves other entities or services, then the enum is doing too much.