patternjavaMinor
An attempt to extend an enum to prevent branching without violating the Open-Closed principle
Viewed 0 times
preventextendwithoutviolatingtheprincipleenumopenclosedattempt
Problem
I love making utility State enums, and giving them methods that do useful things based on which instance of the enum is provided. This works when the enum is very specific to the class I'm working on, but can often make no sense when I need to switch on the enum type but I don't want the enum itself to know anything about that functionality. Further, I don't want to violate the open-closed principle in my main enum. I would prefer to avoid doing
Here's my solution that attempts to get the best of both worlds. It works (which is why I'm on CodeReview and not Programmers or SO), but it's clunky. Notice that I never have to change
```
public class EnumExample {
public static interface OneEnumVisitor {
R visitFoo(A args);
R visitBar(A args);
}
public static interface TwoEnumVisitor {
R visitFoo(A argOne, B argTwo);
R visitBar(A argOne, B argTwo);
}
public static enum MyEnum {
FOO {
public R visit(OneEnumVisitor visitor, A args) {
return visitor.visitFoo(args);
}
public R visit(TwoEnumVisitor visitor, A argOne, B argTwo) {
return visitor.visitFoo(argOne, argTwo);
}
},
BAR {
public R visit(OneEnumVisitor visitor, A args) {
return visitor.visitBar(args);
}
public R visit(TwoEnumVisitor visitor, A argOne, B argTwo) {
return visitor.visitBar(argOne, argTwo);
}
};
public abstract R visit(OneEnumVisitor visitor, A args);
public abstract R visit(TwoEnumVisitor visitor, A argOne, B argTwo);
}
public static enum NewMethod implements TwoEnumVisitor {
INSTANCE;
@Override
public Integer visitFoo(Integer argOne, Integer argTwo) {
return argOne.intValue() - argTwo.intValue();
}
@Override
public Integer visitBar(Integer argOne, Integer
switch...case on the enum if possible.Here's my solution that attempts to get the best of both worlds. It works (which is why I'm on CodeReview and not Programmers or SO), but it's clunky. Notice that I never have to change
MyEnum again, all I do is create a new helper class each time I have a new behavior that I "wish" MyEnum had. Is there a better way?```
public class EnumExample {
public static interface OneEnumVisitor {
R visitFoo(A args);
R visitBar(A args);
}
public static interface TwoEnumVisitor {
R visitFoo(A argOne, B argTwo);
R visitBar(A argOne, B argTwo);
}
public static enum MyEnum {
FOO {
public R visit(OneEnumVisitor visitor, A args) {
return visitor.visitFoo(args);
}
public R visit(TwoEnumVisitor visitor, A argOne, B argTwo) {
return visitor.visitFoo(argOne, argTwo);
}
},
BAR {
public R visit(OneEnumVisitor visitor, A args) {
return visitor.visitBar(args);
}
public R visit(TwoEnumVisitor visitor, A argOne, B argTwo) {
return visitor.visitBar(argOne, argTwo);
}
};
public abstract R visit(OneEnumVisitor visitor, A args);
public abstract R visit(TwoEnumVisitor visitor, A argOne, B argTwo);
}
public static enum NewMethod implements TwoEnumVisitor {
INSTANCE;
@Override
public Integer visitFoo(Integer argOne, Integer argTwo) {
return argOne.intValue() - argTwo.intValue();
}
@Override
public Integer visitBar(Integer argOne, Integer
Solution
It's really hard to tell which parts of your code are the problem you are trying to solve, and which parts are the example. I think that's a hint that the solution has made things worse, rather than better.
Attempting to summarize your constraint - you want to use an enum to select the appropriate behavior to use at some point in the code, but you don't want to use a switch statement, and you don't want to have to modify the enum to achieve the new behavior.
If so, I think an
If you really want to use a visitor based approach, you should probably refactor the abstract methods to an interface; which would allow you to extend the existing vocabulary of enums without actually needing to change MyEnum at all.
Which in turn allows other programmers to introduce their own enums that can be used by SomeObject
Attempting to summarize your constraint - you want to use an enum to select the appropriate behavior to use at some point in the code, but you don't want to use a switch statement, and you don't want to have to modify the enum to achieve the new behavior.
If so, I think an
EnumMap should serve nicely.private static final EnumMap> lookup = new EnumMap (MyEnum.class);
static {
lookup.put(MyEnum.FOO, new TwoArgFunction () {
Integer apply (Integer lhs, Integer rhs) {
return lhs.intValue() - rhs.intValue();
}
) ;
lookup.put(MyEnum.BAR, new TwoArgFunction () {
Integer apply (Integer lhs, Integer rhs) {
return lhs.intValue() + rhs.intValue();
}
) ;
}
public static class SomeObject {
...
public int doMath(int value) {
return lookup.get(type).apply(value, param);
}
}If you really want to use a visitor based approach, you should probably refactor the abstract methods to an interface; which would allow you to extend the existing vocabulary of enums without actually needing to change MyEnum at all.
interface Visitable {
public abstract R visit(OneEnumVisitor visitor, A args);
public abstract R visit(TwoEnumVisitor visitor, A argOne, B argTwo);
}
public static enum MyEnum implements Visitable {
FOO { ... },
BAR { ... };
}
public static class SomeObject {
private final Visitable type;
private final int param;
public SomeObject(Visitable type, int param) {
this.type = type;
this.param = param;
}
public int doMath(int value) {
return type.visit(NewMethod.INSTANCE, value, param);
}
}Which in turn allows other programmers to introduce their own enums that can be used by SomeObject
public static enum HisEnum implements Visitable {
BAZ {...}
}Code Snippets
private static final EnumMap<MyEnum, TwoArgFunction<Integer,Integer,Integer>> lookup = new EnumMap<MyEnum, TwoArgFunction> (MyEnum.class);
static {
lookup.put(MyEnum.FOO, new TwoArgFunction<Integer,Integer,Integer> () {
Integer apply (Integer lhs, Integer rhs) {
return lhs.intValue() - rhs.intValue();
}
) ;
lookup.put(MyEnum.BAR, new TwoArgFunction<Integer,Integer,Integer> () {
Integer apply (Integer lhs, Integer rhs) {
return lhs.intValue() + rhs.intValue();
}
) ;
}
public static class SomeObject {
...
public int doMath(int value) {
return lookup.get(type).apply(value, param);
}
}interface Visitable {
public abstract <R, A> R visit(OneEnumVisitor<R, A> visitor, A args);
public abstract <R, A, B> R visit(TwoEnumVisitor<R, A, B> visitor, A argOne, B argTwo);
}
public static enum MyEnum implements Visitable {
FOO { ... },
BAR { ... };
}
public static class SomeObject {
private final Visitable<Integer,Integer,Integer> type;
private final int param;
public SomeObject(Visitable<Integer,Integer,Integer> type, int param) {
this.type = type;
this.param = param;
}
public int doMath(int value) {
return type.visit(NewMethod.INSTANCE, value, param);
}
}public static enum HisEnum implements Visitable {
BAZ {...}
}Context
StackExchange Code Review Q#54560, answer score: 2
Revisions (0)
No revisions yet.