if-else 대신 Enum 사용하기

Enum

Enum 타입을 사용하면 관련 상수/기능들을 한곳에 모아두고 사용할 수 있다. 관련 데이터들을 모아두니 수정/확장하기에 편리하다. 특히 분기문 처리가 필요할 때, 가독성이 떨어지는 if-else 대신 Enum을 활용하면 코드도 훨씬 깔끔해진다.

우선 유용하게 쓸 수 있는 메소드 몇 가지를 소개해보고, 실제 if-else를 대체해서 얼마나 코드가 깔끔해지는 지 정리해보았다.

1. 상수

아래 예제에선 Enum 타입별로 관련 상수(가격과 한글메뉴명)가 할당되어있다. 여기서 Enum 내장 메소드를 활용하면 여러 기능을 처리할 수 있다.

public enum Menu {
    AMERICANO(4000, "아메리카노"),
    CAFE_LATTE(4500, "카페라떼"),
    CAFE_MOCHA(5000, "카페모카"),
    CAPPUCCINO(6000, "카푸치노"),
    TEA(5500, "티");

    private final int money;
    private final String korean;

    Menu(int money, String korean) {
        this.money = money;
        this.korean = korean;
    }
}

우선 가장 간단하게는, 아래 getMoney()처럼 타입별 상수값을 반환할 수도 있다. 타입별 상수값을 찾을 때 사용할 수 있다.

// Enum 클래스
public int getMoney() {
    return money;
}

// Test 코드
@Test
void test() {
    Menu tea = Menu.TEA;
    int result = tea.getMoney();

    Assertions.assertThat(result).isEqualTo(5500);
}

그리고 Enum은 values() 라는 메소드를 지원하는 데, 이는 모든 Enum 타입들을 리스트로 반환해준다.

즉, Menu.values()는 모든 Menu타입을 가진 리스트이다. 이 메소드를 활용하면 상수값으로 해당 Enum 타입을 찾을 수도 있다!

System.out.println(Arrays.toString(Menu.values()));

// 출력 : [AMERICANO, CAFE_LATTE, CAFE_MOCHA, CAPPUCCINO, TEA]

상수값으로 타입 반환

아래처럼 한글 메뉴명만으로 해당 타입을 반환할 수 있다. (→ 이 방식이 if-else를 사용하지 않고 분기처리를 할 수 있게 해준다. 글 마지막에 다시 예제를 써놓았다!)

// Enum 클래스
public static Menu getMenuByKorean(String korean) {    
    return Arrays.stream(values()) // values() : 모든 Enum 타입 리스트 반환
            .filter(i -> korean.equals(i.korean))
            .findFirst()
            .orElse(null);
}

// Test 코드
@Test
void test() {
    Menu findMenu = Menu.getMenuByKorean("아메리카노");

    Assertions.assertThat(findMenu).isEqualTo(Menu.AMERICANO);
}

타입의 존재여부

해당 타입의 존재여부도 확인할 수 있다.

// Enum 클래스
public static Boolean isMenuExist(String korean) {
    return Arrays.stream(values())
        .anyMatch(value -> korean.equals(value.korean));
}


// Test 코드
@Test
void test() {
    Boolean isAmericanoExist = Menu.isMenuExist("아메리카노");
    Boolean isMilkTeaExist = Menu.isMenuExist("밀크티");

    Assertions.assertThat(isAmericanoExist).isTrue();
    Assertions.assertThat(isMilkTeaExist).isFalse();
 }

타입별로 같은 연산일 경우

이 예제처럼 메뉴별로 연산이 같을 경우, 갯수를 입력하면 메뉴 가격대로 곱해 최종 금액을 반환하는 메소드를 만들 수 있다.

public enum Menu {
    AMERICANO(4000, "아메리카노"),
    CAFE_LATTE(4500, "카페라떼"),
    CAFE_MOCHA(5000, "카페모카"),
    CAPPUCCINO(6000, "카푸치노"),
    TEA(5500, "티");

    ...

    public int calculateMoney (int count){ // 
        return money * count;
    }
}

@Test
void test3() {
    int result = Menu.AMERICANO.calculateMoney(2);
    Assertions.assertThat(result).isEqualTo(8000);
}

근데 만약 이 예제처럼 연산식이 다 money * count 이 아니라, Enum 타입별로 다르면 어떻게 될까? ⇒ 아예 연산식을 상수화해주거나 오버라이딩해주어야 한다!

2. 연산

Enum 타입별로 연산식이 다를 경우, 연산식 자체를 상수화해주거나, 오버라이딩해줄 수 있다. 아래는 사격 게임에서 사용자가 맞춘 과녁이 어느 구역이냐에 따라 얻는 점수가 다르다고 가정한 상황이다.

연산식 상수화

public enum Prize {

	BASIC(i -> i),
	DOUBLE(i -> i * 2),
	BONUS(i -> i + 50),
	DOUBLE_AND_BONUS(i -> i * 3);

    private Function<Integer, Integer> expression;

    Prize(Function<Integer, Integer> expression) {
        this.expression = expression;
    }

    public Integer calculate(int count) {
        return expression.apply(count);
    }
}

오버라이딩

public enum Prize {

	BASIC{
        @Override
        Integer calculate(int count) {
            return count;
        };
    },
	DOUBLE{
        @Override
        Integer calculate(int count) {
            return count * 2;
        };
    },
	BONUS{
        @Override
        Integer calculate(int count) {
            return count + 50;
        };
    },
	DOUBLE_AND_BONUS{
        @Override
        Integer calculate(int count) {
            return count * 3;
        };
    };

    abstract Integer calculate(int count);
}

두가지 방법 모두 해당 타입.calculate() 으로 사용하면 된다.

@Test
void test() {
    int resultDouble = Prize.DOUBLE.calculate(100);
    int resultBONUS = Prize.BONUS.calculate(100);

    Assertions.assertThat(resultDouble).isEqualTo(200);
    Assertions.assertThat(resultBONUS).isEqualTo(150);
}

3. Enum으로 if-else 대신하기

위에서 사격 구역 별로 점수가 다르게 반환되는 상황에서 if-else와 Enum을 비교해보았다.

if-else만 사용

if- else문만으로 구현하면 아래처럼 코드가 길고 복잡해진다. 가독성이 떨어지고, 각 분기 상황마다 뭔가를 수정해줄 때도 불편하다.

// String korean: 맞춘 구역 (“베이직”, "더블", "보너스", "더블보너스")
// int count: 몇번 맞췄는 지
void getResult(String korean, int count) {
    int result;

    if (korean.equals("베이직")) {
        result =  count;
    } else if (korean.equals("더블")) {
        result =  count * 2;
    } else if (korean.equals("보너스")) {
        result =  count + 50;
    } else if (korean.equals("더블보너스")) {
        result =  count * 3;
    }
    return result;
}

Enum 사용

Enum을 사용해서 관련 상수/연산식을 다 모아두었다. 여기서는 연산식 자체를 상수화했다.

calculateByKorean(String korean, int count)메소드를 보면, korean값(한글 구역명)으로 Enum 타입을 찾고, 그 타입에 맞는 연산을 한다.

public enum Prize {

    BASIC("베이직", i -> i),
    DOUBLE("더블", i -> i * 2),
    BONUS("보너스", i -> i + 50),
    DOUBLE_AND_BONUS("더블보너스", i -> i * 3);

    private Function<Integer, Integer> expression;
    private String korean;

    Prize(String korean, Function<Integer, Integer> expression) {
        this.korean = korean;
        this.expression = expression;
    }

    public Integer calculate(int count) {
        return expression.apply(count);
    }

    public static Integer calculateByKorean(String korean, int count) {
        return Arrays.stream(values())
                .filter(i -> i.korean.equals(korean))
                .findFirst()
                .orElse(null)
                .calculate(count);
    }
}

이렇게 Enum을 활용하면, 아래처럼 if-else 분기문 없이 한줄로 값을 찾을 수 있다!

// String korean: 맞춘 구역 (“베이직”, "더블", "보너스", "더블보너스")
// int count: 몇번 맞췄는 지
Integer getResult(String korean, int count) {
    return Prize.calculateByKorean(korean, count);
}

Last updated