포스트

열거형 - Enum

열거형 - Enum

문자열과 타입 안전성

비즈니스 요구 사항

고객은 3등급으로 나누고, 상품 구매시 등급별로 할인을 적용한다. 할인시 소수점 이하는 버린다.

  • BASIC 10% 할인
  • GOLD 20% 할인
  • DIAMOND 30% 할인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DiscountService {

  public int discount(String grade, int price) {
    int discountPercent = 0;

    if (grade.equals("BASIC")) {
      discountPercent = 10;
    } else if (grade.equals("GOLD")) {
      discountPercent = 20;
    } else if (grade.equals("DIAMOND")) {
      discountPercent = 30;
    } else {
      System.out.println(grade + ": 할인X");
    }

    return price * discountPercent / 100;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StringGradeEx0_2 {

  public static void main(String[] args) {
    int price = 10000;

    DiscountService discountService = new DiscountService();

    // 존재하지 않는 등급
    int vip = discountService.discount("VIP", price);
    System.out.println("VIP 등급의 할인 금액: " + vip);

    // 오타
    int diamondd = discountService.discount("DIAMONDD", price);
    System.out.println("DIAMONDD 등급의 할인 금액: " + diamondd);

    // 소문자 입력
    int gold = discountService.discount("gold", price);
    System.out.println("gold 등급의 할인 금액: " + gold);
  }
}

위와 같이 값을 잘못 넣을 가능성이 있습니다.

나름대로의 해결방안

1
2
3
4
5
public class StringGrade {
  public static final String BASIC = "BASIC";
  public static final String GOLD = "GOLD";
  public static final String DIAMOND = "DIAMOND";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DiscountService {

  //StringGrade를 참고하세요.
  public int discount(String grade, int price) {
    int discountPercent = 0;

    if (grade.equals(StringGrade.BASIC)) {
      discountPercent = 10;
    } else if (grade.equals(StringGrade.GOLD)) {
      discountPercent = 20;
    } else if (grade.equals(StringGrade.DIAMOND)) {
      discountPercent = 30;
    } else {
      System.out.println(grade + ": 할인X");
    }

    return price * discountPercent / 100;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class StringGradeEx1_1 {

  public static void main(String[] args) {
    int price = 10000;

    DiscountService discountService = new DiscountService();
    int basic = discountService.discount(StringGrade.BASIC, price);
    int gold = discountService.discount(StringGrade.GOLD, price);
    int diamond = discountService.discount(StringGrade.DIAMOND, price);

    System.out.println("BASIC 등급의 할인 금액: " + basic);
    System.out.println("GOLD 등급의 할인 금액: " + gold);
    System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
  }
}

여기까지 보면 아주 좋았지만 String이라는 타입 때문에 여전히 동일한 문제가 발생합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StringGradeEx1_2 {

  public static void main(String[] args) {
    int price = 10000;

    DiscountService discountService = new DiscountService();

    // 존재하지 않는 등급
    int vip = discountService.discount("VIP", price);
    System.out.println("VIP 등급의 할인 금액: " + vip);

    // 오타
    int diamondd = discountService.discount("DIAMONDD", price);
    System.out.println("DIAMONDD 등급의 할인 금액: " + diamondd);

    // 소문자 입력
    int gold = discountService.discount("gold", price);
    System.out.println("gold 등급의 할인 금액: " + gold);
  }
}

타입 안전 열거형 패턴

1
2
3
4
5
6
7
8
9
public class ClassGrade {
  public static final ClassGrade BASIC = new ClassGrade(); //x001
  public static final ClassGrade GOLD = new ClassGrade(); //x002
  public static final ClassGrade DIAMOND = new ClassGrade(); //x003

  //private 생성자 추가
  private ClassGrade() {
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DiscountService {

  public int discount(ClassGrade classGrade, int price) {
    int discountPercent = 0;

    if (classGrade == ClassGrade.BASIC) {
      discountPercent = 10;
    } else if (classGrade == ClassGrade.GOLD) {
      discountPercent = 20;
    } else if (classGrade == ClassGrade.DIAMOND) {
      discountPercent = 30;
    } else {
      System.out.println("할인X");
    }

    return price * discountPercent / 100;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ClassGradeEx2_1 {

  public static void main(String[] args) {
    int price = 10000;

    DiscountService discountService = new DiscountService();
    int basic = discountService.discount(ClassGrade.BASIC, price);
    int gold = discountService.discount(ClassGrade.GOLD, price);
    int diamond = discountService.discount(ClassGrade.DIAMOND, price);

    System.out.println("BASIC 등급의 할인 금액: " + basic);
    System.out.println("GOLD 등급의 할인 금액: " + gold);
    System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
  }
}

이제는 실수 할 수가 없게 되었습니다.

장점

  • 타입 안정성 향상
  • 데이터 일관성
  • 제한된 인스턴스 생성

단점

  • 생각보다 많은 코드 작성
  • private 생성자를 추가하는 등 유의해야 하는 부분 존재

Enum Type

1
2
3
public enum Grade {
  BASIC, GOLD, DIAMOND
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DiscountService {

  public int discount(Grade grade, int price) {
    int discountPercent = 0;

    if (grade == Grade.BASIC) {
      discountPercent = 10;
    } else if (grade == Grade.GOLD) {
      discountPercent = 20;
    } else if (grade == Grade.DIAMOND) {
      discountPercent = 30;
    } else {
      System.out.println("할인X");
    }

    return price * discountPercent / 100;
  }
}

이렇게 하면 타입 안전 열거형 패턴과 동일한 효과를 얻을 수 있습니다.

1
2
3
4
5
6
7
8
9
public class Grade extends Enum {
  public static final Grade BASIC = new Grade();
  public static final Grade GOLD = new Grade();
  public static final Grade DIAMOND = new Grade();

  //private 생성자 추가
  private Grade() {
  }
}
  • 열거형도 클래스
  • 내부적으로 Enum 클래스 상속
  • 외부에서 임의로 생성 불가

장점

  • 타입 안정성 향상: 사전에 정의된 상수들로만 구성되므로, 유효하지 않은 값이 입력될 가능성이 없다.
  • 간결성 및 일관성: 코드가 더 간결하고 명확해지며, 데이터의 일관성 보장
  • 확장성: 새로운 회원 등급을 타입을 추가하고 싶을 때, Enum에 새로운 상수를 추가

리팩토링

변수 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum Grade {
  BASIC(10), GOLD(20), DIAMOND(30);

  private final int discountPercent; // 추가

  Grade(int discountPercent) {
    this.discountPercent = discountPercent;
  }

  public int getDiscountPercent() {
    return discountPercent;
  }

}

1
2
3
4
5
6
public class DiscountService {

  public int discount(Grade grade, int price) {
    return price * grade.getDiscountPercent() / 100;
  }
}

메서드 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum Grade {
  BASIC(10), GOLD(20), DIAMOND(30);

  private final int discountPercent;

  Grade(int discountPercent) {
    this.discountPercent = discountPercent;
  }

  public int getDiscountPercent() {
    return discountPercent;
  }

  //추가
  public int discount(int price) {
    return price * discountPercent / 100;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EnumRefMain3_4 {

  public static void main(String[] args) {
    int price = 10000;
    Grade[] grades = Grade.values();
    for (Grade grade : grades) {
      printDiscount(grade, price);
    }
  }

  private static void printDiscount(Grade grade, int price) {
    System.out.println(grade.name() + " 등급의 할인 금액: " + grade.discount(price));
  }
}

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.