Enum에서 메서드 참조와 BiFunction 사용하기

요약

int matchCount와 boolean bonusMatch를 BiFunction를 통해 해당 Enum 객체를 찾을 수 있다.

문제 배경 (안좋은 사례 모음)

로또 순위를 Enum LottoRank로 등수와 상금을 관리하고자 한다.
LottoRank가 맞춘 번호 갯수와 보너스 번호 맞춤여부를 전달받아 등수를 판별해야 한다.

  1. LottoRank에 맞춘 번호 갯수와 보너스 번호 맞춤여부를 추가로 저장하면 된다?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public enum LottoRank {
    FIRST(new Money(2_000_000_000), 6, false),
    SECOND(new Money(30_000_000),5, true),
    THIRD(new Money(1_500_000), 5, false),
    FOURTH(new Money(50_000), 4, false),
    FIFTH(new Money(5_000), 3, false),
    NOTHING(Money.ZERO, 2, false);
    //이하 생략...
    }

    그런데 문제가 있다. 4등, 5등은 보너스 맞춤여부가 false이든 true이든 상관없이 맞춘 갯수만 중요하다.
    그리고 꽝인 경우, 맞춘 갯수가 2개 뿐만 아니라 1개, 0개여도 꽝이다. 그리고 꽝도 4등 5등의 경우와 마찬가지로 보너스 맞춤여부가 상관없다.
    이런 경우도 전부 알맞은 상금을 가진 열거형 객체를 반환해줘야 한다.

  2. 좀 더 순위가 나오는 경우를 구체화한다?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public enum LottoRank {
    FIRST(new Money(2_000_000_000), 6, false),
    SECOND(new Money(30_000_000),5, true),
    THIRD(new Money(1_500_000), 5, false),
    FOURTH(new Money(50_000), 4, false),
    FOURTH_WITH_BONUS(new Money(50_000), 4, true),
    FIFTH(new Money(5_000), 3, false),
    FIFTH_WITH_BONUS(new Money(5_000), 3, true),
    NOTHING_TWO(Money.ZERO, 2, false);
    NOTHING_TWO_WITH_BONUS(Money.ZERO, 2, true);
    NOTHING_ONE(Money.ZERO, 1, false);
    NOTHING_ONE_WITH_BONUS(Money.ZERO, 1, true);
    NOTHING_(Money.ZERO, 0, false);
    NOTHING_WITH_BONUS(Money.ZERO, 0, true);
    //이하 생략...
    }

    음…이렇게 하면 당연히 안된다!!!!!!!
    물론 보너스 번호 일치여부를 또 다른 열거형으로 도입하면 보너스 맞춤여부가 상관없는 경우 BONUS_MATCH.NOT_NEED 이런 식으로 처리하면 되긴 할 것이다.
    그렇지만 NOTHING의 맞춘 갯수가 0 ~ 2개를 모두 적용되어야 된다.

BiFunction 도입하기

이제 생각을 좀 바꿔보자.
열거형 객체가 순위를 정하는 기준을 메서드참조로 저장하고 있다면 어떨까?

1. 일단 등수를 구별하는 기준을 메서드로 만들어주자

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
public enum LottoRank {
//생략...

private static boolean isFirstPrize(Integer matchCount, boolean bonusMatch) {
return matchCount == 6;
}

private static boolean isSecondPrize(Integer matchCount, boolean bonusMatch) {
return matchCount == 5 && bonusMatch;
}

private static boolean isThirdPrize(Integer matchCount, boolean bonusMatch) {
return matchCount == 5 && !bonusMatch;
}

private static boolean isFourthPrize(Integer matchCount, boolean bonusMatch) {
return matchCount == 4;
}

private static boolean isFifthPrize(Integer matchCount, boolean bonusMatch) {
return matchCount == 3;
}

private static boolean isNothingPrize(Integer matchCount, boolean bonusMatch) {
return 0 <= matchCount && matchCount < 3;
}
}

이렇게 각 등수를 구분하는 기준을 정해준다.
물론 bonusMatch를 사용하지 않는 경우도 있다. 하지만 나중에 메서드 참조를 위해서 넣어줬다.

2. BiFunction을 메서드 참조를 통해 열거형의 필드로 보내준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum LottoRank {
FIRST(new Money(2_000_000_000), LottoRank::isFirstPrize),
SECOND(new Money(30_000_000), LottoRank::isSecondPrize),
THIRD(new Money(1_500_000), LottoRank::isThirdPrize),
FOURTH(new Money(50_000), LottoRank::isFourthPrize),
FIFTH(new Money(5_000), LottoRank::isFifthPrize),
NOTHING(Money.ZERO, LottoRank::isNothingPrize);

private final Money prize;
private final BiFunction<Integer, Boolean, Boolean> predicate;

LottoRank(Money prize, BiFunction<Integer, Boolean, Boolean> predicate) {
this.prize = prize;
this.predicate = predicate;
}
// 이하 생략
}

두가지 객체를 받아서 한가지로 결과를 반환하는 BiFunction 인터페이스를 필드로 가지고, 생성자로 초기화해주자.
그리고 우리가 만들었던 메서드들이 메서드 참조로 BiFunction을 구현하게 된다!!

3. 각 객체의 BiFunction을 통해 적절한 순위 객체를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum LottoRank {
//열거형의 필드와 생성자는 위에 나와있으니 생략.

public static LottoRank of(Integer matchCount, boolean bonusMatch) {
return Stream.of(values())
.filter(rank -> rank.isMatched(matchCount, bonusMatch))
.findFirst()
.orElseThrow(InvalidMatchCountException::new);
}

//등수 기준 구현 메서드는 위에 나와있으니 생략.

private boolean isMatched(int matchCount, boolean bonusMatch) {
return predicate.apply(matchCount, bonusMatch);
}
}

LottoRank의 모든 객체들을 순회하면서 그 객체의 BiFunction에 판별에 필요한 정보를 전달해줘서 적절한 순위를 찾아낸다!

Share