Diasiare
Diasiare

Reputation: 755

Tying additional data to each value of an enum

As we all know it is impossible to extend an enum, for very good reasons. However I have run into a case where I need to add some additional information to each value of an already implemented enum that comes from a separate library, this library is in my control but should not contain information specific to the application I am developing at the moment.

Example

Let's say I use a library that provides a day of week enum like so:

public enum DayOfWeek {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;
}

Other classes in this external library use this enum extensively.

Now let's say that we are writing a appointment scheduling application, so we want to somehow tie a closing time to each value of DayOfWeek, if DayOfWeek was a part of our application we could just a add a field and a constructor to it but that is not an option here.

Important considerations

  1. Whatever solution we pick should act as much as possible like an instance of DayOfWeek. Preferably I should be able to pass around an object which can be used as a DayOfWeek but that also contains a closing time.
  2. Missing value detection. If a value is added to or removed from DayOfWeek (maybe aliens with an 8 day week invaded or something) then our implementation should fail as early as possible until a corresponding closing time is added to our library. Preferably at compile time.
  3. Ease of use. It should be obvious how to get a closing time from a DayOfWeek for a new developer of our application.
  4. Maintainability. It should be as difficult as possible to mess up making changes to our implementation.
  5. Ease of development. This is the least important but a good solution shouldn't be too difficult to implement.

What I have tried

My initial thoughts on this was a simple switch statement in a helper method Like as follows:

public static String getClosingTime(DayOfWeek day) {
    switch (day) {
    case MONDAY:
    case TUESDAY:
    case WEDNESDAY:
    case THURSDAY:
        return "17:30";
    case FRIDAY:
        return "16:00";
    case SATURDAY:
    case SUNDAY:
        return "Closed";
    default:
        return null;
    }
}

(Return values are String's for simplicity, they should obviously be some kind of object in a real world case).

This succeeds on point 3 to 5 but is burdensome to use as this method has to be called every time a closing time is needed.

Upvotes: 2

Views: 367

Answers (3)

Stefan Warminski
Stefan Warminski

Reputation: 1835

If you don't have to use DayOfWeek directly I would prefer an interface (in the same package as your enum DayOfWeek) like

public interface IDayOfWeek {

    DayOfWeek getDayOfWeek();
}

and edit your enum to

public enum DayOfWeek implements IDayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    @Override
    public DayOfWeek getDayOfWeek() {
        return this;
    }
}

Now you're able to craete a new enum like

public enum DayOfWeekAndClosingTime implements IDayOfWeek {
    MONDAY(DayOfWeek.MONDAY, "17:30"),
    TUESDAY(DayOfWeek.TUESDAY, "17:30"),
    WEDNESDAY(DayOfWeek.WEDNESDAY, "17:30"),
    THURSDAY(DayOfWeek.THURSDAY, "17:30"),
    FRIDAY(DayOfWeek.FRIDAY, "16:00"),
    SATURDAY(DayOfWeek.SATURDAY, "Closed"),
    SUNDAY(DayOfWeek.SUNDAY, "Closed");

    private final DayOfWeek day;
    private final String closingTime;

    DayOfWeekAndClosingTime(DayOfWeek day, String closingTime) {
        this.day = day;
        this.closingTime = closingTime;
    }

    @Override
    public DayOfWeek getDayOfWeek() {
        return day;
    }

    public String getClosingTime() {
        return closingTime;
    }
}

Now you can treat your additional information as a day of week:

public void handleDayOfWeek(IDayOfWeek day) {
    DayOfWeek dayOfWeek = day.getDayOfWeek();
    // do something
    if (day instanceof DayOfWeekAndClosingTime)
        handleClosingTime((DayOfWeekAndClosingTime) day);
}

The negative point is the cross reference between DayOfWeek and IDayOfWeek

EDIT: to check added values in DayOfWeek you can add a static block

static {
    if (DayOfWeekAndClosingTime.values().length != DayOfWeek.values().length)
        throw new IllegalStateException("missing closing times");
}

There you could also check if all days of your wrapping enum are disjunct.

EDIT 2: Thanks to RobertKock

To get a compiler warning you can reduce the constructor by the String parameter and change it to

DayOfWeekAndClosingTime(DayOfWeek day) {
    this.day = day;
    String time = "unknown";
    switch(day) {
        case MONDAY:
        case TUESDAY:
        case WEDNESDAY:
        case THURSDAY:
            time = "17:30";
            break;
        case FRIDAY:
            time = "16:00";
            break;
        case SATURDAY:
        case SUNDAY:
            time = "closed";
            break;
    }
    this.closingTime = time;
}

Upvotes: 3

lexicore
lexicore

Reputation: 43651

Here's a little bit crazy idea. You can extend your enum to accept a visitor.

public interface DayOfWeekVisitor<R> {
    public R onMonday();
    public R onTuesday();
    // ...
}

public enum DayOfWeek {
    MONDAY {
        public <R> R accept(DayOfWeekVisitor<R> visitor) { return visitor.onMonday(); }
    },
    // ...
    ;
    public <R> R accept(DayOfWeekVisitor<R> visitor);
}

Then for all of your DayOfWeek-dependent operations you'll implement specific visitors:

public class ClosingTimeVisitor extends DayOfWeekVisitor<String> {
    public String onMonday() { return "17:30"; }
    // ...
}

Yes, this extends the enum - but not with any application-specific things.

How does it fit your requirements?

  1. I think dayOfWeek.visit(ClosingTimeVisitor.INSTANCE) is, well, OK. Not brilliant but fine.
  2. Missing value detection. If we (hopefully) get invaded by aliens who also have Bumbsday, you'll add onBumbsday to your DayOfWeekVisitor interface. This will immediately force you - per compiler error - to implement this method in all the DayOfWeekVisitor implementations you have.
  3. Ease of use. dayOfWeek.visit(ClosingTimeVisitor.INSTANCE) or even with some syntactic sugar dayOfWeek.calculate(CLOSING_TIME) seems quite easy for me. Not the usual thing for starters but I think it's quite easy to understand the concept.
  4. Maintainability. It's a bit har to evaluate this. But I think it will be fine. At least better than maintaining mappings with Map<DayOfWeek, String> and likes.
  5. Ease of development. Seems pretty easy to me.

Upvotes: 2

chaitanya89
chaitanya89

Reputation: 847

For these cases, we follow the following implementation.

public enum DayOfWeek {

    MONDAY(null),
    TUESDAY(null),
    WEDNESDAY(null),
    THURSDAY("17:30"),
    FRIDAY("16:00"),
    SATURDAY(null),
    SUNDAY("Closed");


    private String closingTime;
    public String getClosingTime() {
        return closingTime;
    }


    private DayOfWeek(String closingTime) {
        this.closingTime = closingTime;
    }
}

Here's how to access it.

DayOfWeek.SUNDAY.getClosingTime(); // it returns "Closed"

Upvotes: 0

Related Questions