Post

Item 38 - 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

타입 안전 열거 패턴 vs 열거 타입

타입 안전 열거 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Operation {
    public static final Operation PLUS = new Operation("+", (x, y) -> x + y);
    public static final Operation MINUS = new Operation("-", (x, y) -> x - y);
    public static final Operation TIMES = new Operation("*", (x, y) -> x * y);
    public static final Operation DIVIDE = new Operation("/", (x, y) -> x / y);
	
    private final String symbol;
    private final BiFunction<Double, Double, Double> func;
	
    private Operation(String symbol, BiFunction<Double, Double, Double> func) {
        this.symbol = symbol;
        this.func = func;
    }
	
    public double apply(double x, double y) {
        return this.func.apply(x, y);
    }
}

열거 타입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum Operation {
    PLUS("+", (x, y) -> x + y),
    MINUS("-", (x, y) -> x - y),
    TIMES("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);
	
    private final String symbol;
    private final BiFunction<Double, Double, Double> func;
	
    Operation(String symbol, BiFunction<Double, Double, Double> func) {
        this.symbol = symbol;
        this.func = func;
    }
	
    public double apply(double x, double y) {
        return this.func.apply(x, y);
    }
}
  • 거의 모든 상황에서 타입 안전 열거 패턴보다 열거 타입이 우수하다. 👍
  • 단, 타입 안전 열거 패턴확장이 가능하지만, 열거 타입확장이 불가능하다.
1
2
3
4
5
6
7
8
9
// 타입 안전 열거 패턴은 확장가능
public class ExtendedOperation extends Operation {
    public static final ExtendedOperation EXP = new ExtendedOperation("^", (x, y) -> Math.pow(x, y));
    public static final ExtendedOperation REMAINDER = new ExtendedOperation("%", (x, y) -> x % y);
	
    protected ExtendedOperation(String symbol, BiFunction<Double, Double, Double> func) {
        super(symbol, func);
    }
}
1
2
3
4
// 열거 타입은 확장 불가능 (컴파일 에러)
public enum ExtendedOperation extends Operation {
    // ...
}

🙅 대부분의 상황에선 열거 타입을 확장하는 건 좋지 않다.

  • 확장 타입의 원소는 기반 타입의 원소지만 기반 타입의 원소는 확장 타입의 원소가 아니다.
  • 기반 타입과 확장 타입들의 원소를 모두 순회할 방법이 마땅치 않.
  • 확장성을 높이려면 고려요소가 늘어나 설계와 구현이 복잡해진다.

그래도 확장하고 싶은 경우엔?

인터페이스를 활용하자.

1
2
3
public interface Operation {
    double apply(double x, double y);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum BasicOperation implements Operation {
    PLUS("+", (x, y) -> x + y),
    MINUS("-", (x, y) -> x - y),
    TIMES("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);
	
    private final String symbol;
    private final BiFunction<Double, Double, Double> func;
	
    BasicOperation(String symbol, BiFunction<Double, Double, Double> func) {
        this.symbol = symbol;
        this.func = func;
    }
	
    @Override
    public double apply(double x, double y) {
        return this.func.apply(x, y);
    }
	
    @Override
    public String toString() {
        return symbol;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum ExtendedOperation implements Operation {
    EXP("^", (x, y) -> Math.pow(x, y)), 
    REMAINDER("%", (x, y) -> x % y);
	
    private final String symbol;
    private final BiFunction<Double, Double, Double> func;
	
    ExtendedOperation(String symbol, BiFunction<Double, Double, Double> func) {
        this.symbol = symbol;
        this.func = func;
    }
	
    @Override
    public double apply(double x, double y) {
        return this.func.apply(x, y);
    }
	
    @Override
    public String toString() {
        return symbol;
    }
}

그럼, Operation 인터페이스를 사용하는 곳에서는 기존 연산과 확장한 연산을 모두 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
    double x = 2;
    double y = 4;
    print(BasicOperation.PLUS, x, y);         // 2.000000 + 4.000000 = 6.000000
    print(ExtendedOperation.REMAINDER, x, y); // 2.000000 % 4.000000 = 2.000000
}

public static void print(Operation op, double x, double y) {
    System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

💡 핵심 정리

  • 열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다.
  • 이를 통해 클라이언트는 이 인터페이스를 구현해 자신만의 열거 타입을 만들 수 있다.
  • API가 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다.
This post is licensed under CC BY 4.0 by the author.