본문 바로가기

Dev Book Review/Effective Java

[Effective Java] item 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

1. 열거타입 확장은 하지 말자

열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴(typesafe enum pattern)보다 우수하다.

단점 : 타입 안전 열거 패턴은 확장할 수 있으나 열거 타입은 그렇지 못하다

타입 안전 열거 패턴의 예시

public class DSymbolType{
  private final String type;
  private DSymbolType(String type){
    this.type = type;
  }
  public String toString(){
    return type;
  }
  public static final DSymbolType Terminal = new DSymbolType("Terminal");
  public static final DSymbolType Process = new DSymbolType("Process");
  public static final DSymbolType Decision = new DSymbolType("Decision");
}

열거 타입을 확장하는 건 좋지 않다.

  • 확장 타입의 원소는 기반 타입의 원소로 취급, 그러나 반대는 아니다 (이상함)
  • 기반 타입과 확장 타입의 원소 모두 순회할 방법도 마땅치 않다.
  • 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 복잡하다.

어울리는 쓰임 : 연산 코드 (opcode)

 

2. 열거 타입 확장

아이디어 : 열거 타입이 임의의 인터페이스를 구현할 수 있다.

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;
    }
}
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;
    }
}

새로 작성한 연산은 기존 연산을 쓰던 곳이면 어디든 사용이 가능하다.

Operation 인터페이스를 사용하도록 작성되있기만 하면된다

// 열거 타입의 Class 객체를 이용해 확장된 열거 타입의 모든 원소를 사용하는 예
private 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));
}

<T extends Enum<T> & Operation> : Class 객체가 열거 타입인 동시에 Operation의 하위 타입이어야 한다.

// 컬렉션 인스턴스를 이용해 확장된 열거 타입의 모든 원소를 사용하는 예 (235쪽)
private 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));
}

확장 가능한 열거 타입 흉내내는 방식의 문제점

열거 타입끼리 구현을 상속할 수 없다.

  • 디폴트 구현을 이용해 인터페이스에 추가하는 방법을 사용할 수 없다.
  • 연산 기호를 저장하고 찾는 로직이 BasicOperation ,ExtendedOperation 모두에 들어가야한다.