- 열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하다.
타입 안전 열거 타입
// 이전에 쓰던 방식
public final class Season {
public static final Season SPRING = new Direction("SPRING");
public static final Season SUMMER = new Direction("SUMMER");
public static final Season FALL = new Direction("FALL");
public static final Season WINTER = new Direction("WINTER");
// ...
}
열거 타입의 단점
- 타입 안전 열거 패턴은 확장이 가능하지만 열거 타입은 확장할 수 없다.
- enum 은 java.lang.Enum 을 확장하고 있음.
대부분의 상황에서 열거 타입을 확장하는건 좋지 않은 생각이다.
- 확장한 타입의 원소는 기반 타입의 원소로 취급하지만 그 반대는 성립하지 않는다면 이상하다.
- 상위, 하위타입의 모든 원소를 순회하는 방법이 없다.
- 확장성을 높이려면 고려해야 할 요소가 늘어나 설계와 구현이 더 복잡해진다.
가끔은 enum 을 확장해야 할 수도 있다.
- 연산코드, 기계마다 가지는 기계가 할 수 있는 연산.
- 인터페이스 구현을 통해 해결한다.
- 연산 코드용 인터페이스를 정의하고 열거타입이 구현하도록 한다. 이 열거타입이 표준 구현체 역할을 한다.
// Operation
public interface Operation {
double apply(double x, double y);
}
// BasicOperation
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;
}
}
// ExtendedOperation
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 public String toString() {
return symbol;
}
}
타입 수준으로 다형성을 적용할 수 있다.
// 1. 한정적 타입 매개변수
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(ExtendedOperation.class, x, y);
}
private static <T extends Enum<T> & Operation> void test(
Class<T> opEnumType, double x, double y) {
for (Opertaion op : opEnumType.getEnumConstants())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
- (<T extends Enum & Operation> Class): Class 객체가 열거 타입인 동시에 Operation의 하위 타입이어야 한다는 뜻이다.
// 2. 한정적 와일드 카드 타입
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);
}
private static void test(Collection<? extends Operation> opSet,
double x, double y) {
for (Opertaion op : opSet)
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
열거 타입에서 인터페이스를 이용해 확장 하는 경우 사소한 문제점
- 열거 타입끼리 구현을 상속할 수 없다.
- 아무 상태에도 의존하지 않는 경우에는 디폴트 구현을 이용해 인터페이스에 추가하는 방법이 있다.
자바 라이브러리 예시
package java.nio.file;
/**
* Defines the options as to how symbolic links are handled.
*
* @since 1.7
*/
public enum LinkOption implements OpenOption, CopyOption {
/**
* Do not follow symbolic links.
*
* @see Files#getFileAttributeView(Path,Class,LinkOption[])
* @see Files#copy
* @see SecureDirectoryStream#newByteChannel
*/
NOFOLLOW_LINKS;
}
- OpenOption, CopyOption 을 구현한다.
결론
- 열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다.
- API가 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다.