타입안전 열거패턴과는 다르게 열거 타입은 확장이 불가능하다.
확장은 보통 기존의 값들에 추가적인 값을 추가하여 다른 목적으로 쓸 때 사용하지만, 열거타입은 이렇게 하지 못한다는 점이 타입안전 열거타입과 다른 점이다.
사실 대부분의 상황에서 열거타입을 확장하는 건 좋은 방법이 아니다. 그 이유는 확장한 타입의 원소는 기반타입의 원소로 취급하지만 그 반대는 그렇지 않고, 이를 한꺼번에 순회할 방법도 마땅치 않고, 마지막으로 확장성을 높이려면 고려해야될 요소가 많으므로 열거타입의 확장 자체가 고려하기 힘든 선택지이다.
하지만 opcode, 즉 연산코드를 구현할 때는 열거타입을 확장하는 것이 어울리긴 한다.
왜냐하면 API 가 제공하는 기본연산 외에도 사용자가 임의적으로 확장해서 연산을 추가할 수 있도록 열어줘야 할 때가 있기 때문이다.
이를 그래서 열거타입 자체를 확장하는 것이 아닌 다른 방법으로 비슷한 효과를 내게끔 할 수 있다. 바로 임의의 인터페이스를 구현하여 해당 interface 를 구현하는 열거타입이 확장되는 개념으로 하게끔 하는 것이다.
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) { return x+y; }
},
MINUS("-") {
public double apply(double x, double y) { return x-y; }
},
TIMES("*") {
public double apply(double x, double y) { return x*y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x/y; }
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
위에 기본 opcode 들이 api 로 제공이 되었을 때 user specific 하게 기능을 추가하고 싶을때 아래와 같이 코드를 작성할 수 있다.
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
public double apply(double x, double y) {
return x % y;
}
};
private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override toString(){...}
}
이렇게 새로 작성한 연산은 기존 연산을 쓰던 곳이면 어디든 쓸 수 있다. Basic Operation 이 아닌 Operation 인터페이스를 사용하도록 작성되어 있으면 된다.
개별 인스턴스 수준에서 뿐 아니라 타입 수준에서도 기본 열거타입 대신 확장된 열거타입을 넘겨 확장된 열거 타입의 원소 모두를 사용하게 할 수도 있다.
아래 코드는 앞서 설명했었던 테스트 프로그램을 가져와 ExtendedOperation
의 모든 원소를 테스트하도록 수정한 모습이다.
public static void main (String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(ExtendedOperation.class, x, y);
}
public static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
for (Operation op : opEnumType.getEnumConstants())
System.out.printf("%f %s %f = %f%n",
x, op, y, op.apply(x, y));
}
ExtendedOperation
의 class 리터럴을 넘겨 확장된 연산들이 무엇인지 알려준다.opEnumType
매개변수의 선언은 class 객체가 열거타입인 동시에 operation의 하위 타입임을 한정지은 한정적 타입 토큰이다.public static void main (String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(Arrays.asList(ExtendedOperation.values()), x, y);
}
public static void test(Collection<? extends Operation> opSet, double x, double y) {
for (Operation op : opSet)
System.out.printf("%f %s %f = %f%n",
x, op, y, op.apply(x, y));
}
이 코드는 그나마 덜 복잡하고, test 메서드가 살짝 더 유연해지지만 특정 연산에서는 EnumSet과 EnumMap을 사용하지 못하게 된다.
자바 라이브러리 중에서 java.nio.file.LinkOption
열거 타입은 CopyOption
과 OpenOption
인터페이스를 구현하여 이 패턴을 사용하여 구현했다.