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.