나는 그간의 미션에서 상수를 관리할 때 하나의 클래스에 public static final을 사용해 관리하고 있었다.
1
2
3
4
5
6
7
8
9
10
11
public class GameConstants {
public static final int RANDOM_NUMBER_MIN_RANGE = 0;
public static final int RANDOM_NUMBER_MAX_RANGE = 9;
public static final int MOVE_DISTANCE = 1;
public static final int CAN_MOVE_MIN_VALUE = 4;
public static final int CAR_SIZE_MIN = 2;
public static final int CAR_NAME_MAX_LEN = 5;
private GameConstants() {}
}
Enum 포스팅에서 Enum을 학습하면서 Enum의 장점도 알았다.
하지만 위 코드를 Enum으로 관리했을 때 사용하는 부분에 맘에 들지 않는 부분이 생긴다.
1
2
3
4
5
public void move() {
if (canMove()) {
this.moveDistance += MOVE_DISTANCE;
}
}
정의한 상수를 사용하는 부분에서 static-import를 사용하면 위와 같이 사용할 수 있다. 가독성 측면에서 좋다고 생각한다.
하지만 Enum으로 관리한다면?
1
2
3
4
5
public void move() {
if (canMove()) {
this.moveDistance += MOVE_DISTANCE.getValue();
}
}
get 함수를 호출해야 해당 int 값을 가져올 수 있다.
이러한 부분에서 Enum을 사용하면 단순하게 가독성만 저해하는 것 아닌가? 라는 생각이 들게 된 것이다.
그래서 왜 Enum을 사용해야 돼?
그냥 가독성만 안좋아지는 것 같은데 Enum을 사용하는 것이 왜 좋은 지 알아보기 위해 static final의 문제점을 Enum과 비교하여 알아보자.
- 유형 안정성이 부족하다.
아래 코드를 보자.
1
2
public static final int STATUS_ON = 1;
public static final int STATUS_OFF = 2;
전혀 문제가 없어보인다. 하지만 Enum과 비교하면?
1
2
3
4
5
6
7
8
public enum STATUS {
ON(1),
OFF(2);
private final int status;
/...
}
ON, OFF에 해당하는 값은 모두 int 유형의 값으로 제한된다. 만약 static final 키워드를 통해 관리한다면 String 타입이 들어갈 수도 있고, 이 외 다른 모든 타입의 변수를 할당할 수 있는데 이런 경우 추적에 어려움을 겪을 수 있다.
- 값의 반복이 불가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public enum LottoResultStatus {
FAIL(0, 0, (matchCount, withBonusNumber) -> matchCount < 3),
THREE_MATCH(3, 5_000, (matchCount, withBonusNumber) -> matchCount == 3),
FOUR_MATCH(4, 50_000, (matchCount, withBonusNumber) -> matchCount == 4),
FIVE_MATCH(5, 1_500_000, (matchCount, withBonusNumber) -> matchCount == 5 && !withBonusNumber),
FIVE_MATCH_WITH_BONUS(5, 30_000_000, (matchCount, withBonusNumber) -> matchCount == 5 && withBonusNumber),
ALL_MATCH(6, 2_000_000_000, (matchCount, withBonusNumber) -> matchCount == 6);
private final long matchCount;
private final int prize;
private final BiPredicate<Long, Boolean> checker;
LottoResultStatus(long matchCount, int prize, BiPredicate<Long, Boolean> checker) {
this.matchCount = matchCount;
this.prize = prize;
this.checker = checker;
}
public static LottoResultStatus checkResult(long matchCount, boolean withBonusNum) {
return Arrays.stream(LottoResultStatus.values())
.filter(status -> status.checker.test(matchCount, withBonusNum))
.findAny()
.orElse(FAIL);
}
/...
}
내가 짰던 로또 결과 Enum 클래스의 일부이다. Enum에 조건식을 삽입해 Enum의 요소를 반복하며 조건에 부합하는 요소가 있는 지 확인하는 메서드를 가지고 있다.
꼭 이러한 조건 검증과 같은 구현이 아니어도, 반복이 필요한 부분은 얼마든지 있다. static final을 사용하면 반복을 위해 더 많은 노력이 필요하다.
- 확장성이 제한되어 있다.
위의 코드에서도 볼 수 있다시피 상수와 관련된 추가 동작을 Enum에서는 쉽게 구현해낼 수 있다. 하지만 static final로 관리했을 때는 불가능하다.
- Enum을 사용했을 때 의미 파악이 훨씬 용이하다.
확실하게 관련된 값들을 관리하게 되어 enum 클래스 네이밍과 함께 의미를 파악하기 훨씬 수월해진다.
- Enum은 값을 제한시킬 수 있다.
1
2
3
4
5
6
7
8
9
public String getResult(String input) {
if (input.equals(InputMessage.HELLO)) {
return "HELLO";
}
if (input.equals(InputMessage.HI)) {
return "HI";
}
}
static final을 사용했을 때는 어떠한 String 값이 들어가도 제한할 수 없게 된다. 하지만 Enum을 사용한다면?
1
2
3
4
5
6
7
8
9
public String getResult(InputMessage input) {
if (input == InputMessage.HELLO) {
return "HELLO";
}
if (input == InputMessage.HI) {
return "HI";
}
}
결론
저번에 포스팅한 Enum의 장점에 더해 static final에 비교한 Enum의 장점까지 알아보았다.
나는 대부분의 상황에서 아마 Enum을 사용하게 될 것 같다.
하지만 단순하게 상수를 정의해야 하는 상황이 온다면 가독성에서 조금이나마 이점이 있는 static final 키워드를 사용해 관리하지 않을까 싶다.