확장할 수 없는 열거 타입
열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하다. 단, 예외가 하나 있으니, 타입 안전 열거 패턴은 확장할 수 있지만, 열거 타입은 그럴 수 없다는 점이다.
확장 시 에러가 발생하는 열거타입
public enum AEnum {
A,B,C
}
public enum BEnum extends AEnum{ // enum은 확장할 수 없다는 에러 발생
D,E,F
}
확장형 열거타입에 어울리는 연산코드
연산 코드의 각 원소는 특정 기계가 수행하는 연산을 뜻한다. 기본으로 더하기, 빼기, 곱하기, 나누기 연산을 제공한다고 가정했을 때 제곱, 나머지 연산과 같은 확장된 연산을 제공해야 할 경우가 있다. 그런데 앞서 말했듯 열거타입은 확장이 불가능하다. 하지만 확장의 효과를 내는 방법이 있다. 바로 인터페이스를 사용하는 것이다. 기본적인 연산에 대한 열거 타입 클래스를 인터페이스를 사용하여 구현하였다.
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation{
PLUS("+"){
@Override
public double apply(double x, double y) {
return x+y;
}
},
MINUS("-"){
@Override
public double apply(double x, double y) {
return x-y;
}
},
TIMES("*"){
@Override
public double apply(double x, double y) {
return x*y;
}
},
DIVIDE("/"){
@Override
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;
}
}
특별 연산이 추가된다면 인터페이스를 구현하자
만약 특별 연산이 추가되어야 한다면 새로운 열거 타입 클래스에서 인터페이스를 구현하면 된다.
public enum ExtendedOperation implements Operation{
EXP("^"){
@Override
public double apply(double x, double y) {
return Math.pow(x,y);
}
},
REMAINDER("%"){
@Override
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;
}
}
테스트
기본 열거 타입 대신 확장된 열거 타입을 넘겨 확장된 열거 타입의 원소 모두를 사용할 수 있다.
public static void main(String[] args) {
test(ExtendedOperation.class, 3, 3);
}
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));
}
}
main 메서드는 test 메서드에 ExtendedOperation의 class 리터럴을 넘겨 확장된 연산들이 무엇인지 알려준다.
타입 매개변수 부분인 <T extends Enum<T> & Operation> 코드는 타입 매개변수 T가 Enum<T>. 즉, 열거 타입임과 동시에 Operation의 하위 타입이어야 한다는 것이다. 이는 Enum을 통한 원소 순회와 Operation 인터페이스의 메서드를 호출하기 위함이다.
이게 복잡하다면 아래 방법을 사용할 수 있다.
public static void main(String[] args) {
test(Arrays.asList(ExtendedOperation.values()), 3, 3);
}
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));
}
}
ExtendedOperation의 상수 인스턴스를 List로 만든 후 각각의 상수 인스턴스에서 값을 순회하며 출력하는 방식이다. 이 코드는 위 방법보다 덜 복잡하고 유연해졌다. Operation을 구현하는 여러 클래스들에서 본인이 필요한 상수 인스턴스들을 추출하여 List로 넘겨주기만 한다면 다양한 연산들을 호출할 수 있기 때문이다.
정리
열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다. 이렇게 하면 클라이언트는 이 인터페이스를 구현해 자신만의 열거 타입을 만들 수 있다. 하지만 이런 구조는 너무 생소할 뿐더러 오히려 시스템의 복잡도를 높힐 수 있다는 생각이 든다. 만약 확장해야한다면 Enum보다는 클래스를 사용하여 구현하는게 더 좋지않을까?? 이번 내용은 크게 와닿지 않는 것 같다.
'공부 > Effective Java' 카테고리의 다른 글
[Effective Java] Item 49. 매개변수가 유효한지 검사하라 (0) | 2024.03.21 |
---|---|
[Effective Java] Item 46. 스트림에서는 부작용 없는 함수를 사용하라 (0) | 2024.01.16 |
[Effective Java] Item 34. int 상수 대신 열거 타입을 사용하라 (1) | 2023.11.08 |
[Effective Java] Item 33. 타입 안전 이종 컨테이너를 고려하라 (0) | 2023.11.08 |
[Effective Java] Item 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 / feat. 매개변수화 타입 (1) | 2023.11.02 |