LakyJ
LakyJ

Reputation: 43

Java: Abstract enum with concrete implementations

Is there a way to create an enum abstraction with different values based on enum names?

Here is a class where I would find something like abstract enum useful:

public class StatusUpdater {
public foo(bool one, bool two, bool three, AbstractEnum status) {
    if (one) {
        caseOne(status);
    }

    if (two) {
        caseTwo(status);
    }

    if (three) {
        caseThree(status);
    }
}

private void caseOne(CaseOneEnum status) {
    status.getValue(); //return case one value + case one implementation
}

private void caseTwo(CaseTwoEnum status) {
    status.getValue(); //return case two value + case two implementation
}

private void caseThree(CaseThreeEnum status) {
    status.getValue(); //return case three value + case three implementation
}

}

One Of the concrete implementation of enum looks like this:

public enum CaseOneEnum {
STATUS_ONE("one"),
STATUS_TWO("two");

private final String status;

CaseOneEnum(final String status) {
    this.status = status;
}

@Override
String getValue(){return status;}

Another implementation will have same enum names, but different values:

public enum CaseTwoEnum {
STATUS_ONE("oneTwo"),
STATUS_TWO("twoTwo");

private final String status;

CaseTwoEnum(final String status) {
    this.status = status;
}

@Override
String getValue(){return status;}

Calling the main method should look something like this:

updater.foo(true, true, false, AbstractEnum.STATUS_ONE);

Is there a way where I could create some "abstract" enum which i could pass to foo() and after checking the case take the concrete implementation of this enum. Enum names for all the concrete enums will stay the same, but values will differ. I would imagine an "abstract" enum something like this:

public enum AbstractEnum {
STATUS_ONE,
STATUS_TWO;

@Override
String getValue();

Is there a way of achieving something like that in a neat way?

Upvotes: 1

Views: 1401

Answers (3)

Holger
Holger

Reputation: 298123

You are mixing up enumerations, which define constants, and associated data. The reason you want to define an "abstract enum", is, that there is only one set of constants, so the actual conclusion is that there should be only one enum.

While enum types allow to hold associated data directly, you should use this feature only for canonical associations. When, like in your case, there are different sets of associated data, you shouldn't store them in the enum type, as that allows only one-to-one relationships. Further, instead of creating another redundant enum type defining identical constants, store the data in an EnumMap, which is specifically designed to associate data with enum constants.

Define a simple enum type as

public enum YourActualEnum {
    STATUS_ONE,
    STATUS_TWO
}

and a class having different associated data, depending on the use case:

public class StatusUpdater {
    private static final EnumMap<YourActualEnum,String> CASE1, CASE2, CASE3;
    static {
        CASE1 = new EnumMap<>(YourActualEnum.class);
        CASE1.put(YourActualEnum.STATUS_ONE, "one");
        CASE1.put(YourActualEnum.STATUS_TWO, "two");
        CASE2 = new EnumMap<>(YourActualEnum.class);
        CASE2.put(YourActualEnum.STATUS_ONE, "oneTwo");
        CASE2.put(YourActualEnum.STATUS_TWO, "twoTwo");
        CASE3 = new EnumMap<>(YourActualEnum.class);
        CASE3.put(YourActualEnum.STATUS_ONE, "oneThree");
        CASE3.put(YourActualEnum.STATUS_TWO, "twoThree");
    }
    public void foo(boolean one, boolean two, boolean three, YourActualEnum status) {
        if (one) {
            caseOne(status, CASE1);
        }

        if (two) {
            caseTwo(status, CASE2);
        }

        if (three) {
            caseThree(status, CASE3);
        }
    }

    private void caseOne(YourActualEnum status, EnumMap<YourActualEnum,String> m) {
        m.get(status);
    }

    private void caseTwo(YourActualEnum status, EnumMap<YourActualEnum,String> m) {
        m.get(status);
    }

    private void caseThree(YourActualEnum status, EnumMap<YourActualEnum,String> m) {
        m.get(status);
    }
}

Upvotes: 0

ernest_k
ernest_k

Reputation: 45309

Here's a solution using an interface. Because enums can implement an interface, you can get rid of the overloaded methods (unless their implementations differ).

interface MyEnumInterface {
    String getValue();
}

With that, your StatusUpdater class becomes:

public class StatusUpdater {
    public void foo(MyEnumInterface status) {
        anyCase(status);
    }

    //You can duplicate this with caseOne, caseTwo methods if
    //implementations are actually different
    private void anyCase(MyEnumInterface status) {
        status.getValue(); 
    }
}

Then you call your foo method in this way:

updater.foo(1, CaseOneEnum.STATUS_ONE);
updater.foo(1, CaseTwoEnum.STATUS_ONE);

And then all that's left is making your enums implement the interface (they already have the method):

enum CaseOneEnum implements MyEnumInterface {
    STATUS_ONE("one"), 
    STATUS_TWO("two");

    private final String status;

    CaseOneEnum(final String status) {
        this.status = status;
    }

    @Override
    public String getValue() {
        return status;
    }
}

//other enums implement the

Upvotes: 1

Sergio Arrighi
Sergio Arrighi

Reputation: 530

indeed all enums are descendants of object Enum and this constitutes already a generic type that you may use to implement a method signature independent from actual enum implementation.

Your code then shows that for each "case" you know exactly which enum to expect and thus you can simply class cast the input object when invoking the dedicated method.

I took your code and modified it a little bit to demonstrate what I'm suggesting.

Two enums implementations

public enum CaseOneEnum {
    STATUS_ONE("one"),
    STATUS_TWO("two");

    private final String status;

    CaseOneEnum(final String status) {
        this.status = status;
    }

    String getValue() {
        return status;
    }
}

public enum CaseTwoEnum {
     STATUS_THREE("three"),
     STATUS_FOUR("four");

     private final String status;

     CaseTwoEnum(final String status) {
         this.status = status;
     }

     String getValue() {
         return status;
     }
 }

The StatusUpdater class

public class StatusUpdater {

    public String foo(int caze, Enum<?> status) {

        if (caze == 1) {
            return caseOne((CaseOneEnum) status);
        }

        if (caze == 2) {
            return caseTwo((CaseTwoEnum) status);
        }
        return null;
    }

    private String caseOne(CaseOneEnum status) {
        return status.getValue();
    }

    private String caseTwo(CaseTwoEnum status) {
        return status.getValue();
    }
}

Most important thing to be noticed is the foo method signature exposing a parameter of type Enum and the class cast when each case method is invoked.

And finally a JUnit to demonstrate.

public class StatusUpdaterTest {

    @Test
    public void testStatusUpdaterCase1() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseOneEnum.STATUS_ONE.getValue(), updater.foo(1, CaseOneEnum.STATUS_ONE));
}

    @Test
    public void testStatusUpdaterCase2() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseOneEnum.STATUS_TWO.getValue(), updater.foo(1, CaseOneEnum.STATUS_TWO));
    }

    @Test
    public void testStatusUpdaterCase3() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseTwoEnum.STATUS_THREE.getValue(), updater.foo(2, CaseTwoEnum.STATUS_THREE));
    }

    @Test
    public void testStatusUpdaterCase4() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseTwoEnum.STATUS_FOUR.getValue(), updater.foo(2, CaseTwoEnum.STATUS_FOUR));
    }
}

Hope this helps!

Cheers

Upvotes: 2

Related Questions