[JAVA] 열거형 ENUM 2

나의 개발 일지·2024년 12월 11일

JAVA

목록 보기
11/11

우선, 글을 작성하기 전 이 글의 모든 내용은 김영한님의 JAVA 강의를 바탕으로 함을 알립니다.

💡열거형 ENUM 2

직전 포스팅에서 고정된 상수 값들의 집합을 정의할 때 String을 사용했을 때의 문제점과 타입 안전 열거형 패턴의 개념을 알아보고 구현을 해보았다. 이번 포스팅에서는 JAVA에서 제공하는 ENUM 열거형 패턴열거형 리팩토링열거형이 제공하는 메서드에 대해 알아보자.

열거형 ENUM Type

JAVA는 타입 안전 열거형 패턴을 쉽게 사용할 수 있도록 ENUM 열거형 타입을 제공한다. 이전 포스팅의 회원 등급과 가격을 입력하면 할인 금액을 계산해주는 예제의 classGradeENUM타입으로 하면 다음과 같다

public enum Grade {
    BASIC, GOLD, DIAMOND  
}

/* Enum은 아래와 같다

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은 자동으로 java.lang.Enum을 상속 받는다
  • 외부에서 임의로 생성할 수 없다 (private 생성자)
  • 열거형은 인터페이스 구현 가능
  • 열거형에 추상 메서드를 선언하고 구현 가능

ENUM은 결국 이전 포스팅의 ClassGrade와 기능이 거의 동일하다고 보면 된다. 다만 개발자가 사용하기 편하게 JAVA에서 직접 제공한다는 점이 차이점이다.

◻️ ENUM 장점

  • 타입 안전성 향상 : 사전에 정의된 상수들로만 구성되기에 유효하지 않은 값이 입력되는 경우는 존재하지 않는다. 이런 경우 컴파일 에러가 발생한다
  • 간결성 및 일관성 : ENUM을 사용하면 코드의 가독성이 높아지고, 타입 안전성 향상에 의해 데이터의 일관성이 보장된다.
  • 확장성 : 새로운 등급을 추가하고 싶을 때, ENUM에 새로운 상수를 추가하면 끝난다.

열거형 주요 메서드

public class EnumMethod1 {

    public static void main(String[] args) {

        // values() : 모든 ENUM 반환
        Grade[] values = Grade.values();
        System.out.println("values = " + Arrays.toString(values)); // values = [BASIC, GOLD, DIAMOND]

        // name() : ENUM 상수 이름 문자열로 반환
        // ordinal() : ENUM 상수의 선언 순서(0부터 시작)를 반환 ,, 사용하지 않을 것을 권장 --> 상수 사이에 새로운 상수가 추가 될 경우 값이 많이 바뀜
        for (Grade value : values) {
            System.out.println("name = " + value.name() + ", ordinal = " + value.ordinal());
            /**
             * name = BASIC, ordinal = 0
             * name = GOLD, ordinal = 1
             * name = DIAMOND, ordinal = 2
             */
        }
        // valueOf(A) : ENUM 상수의 목록 중 A와 일치하는 ENUM 상수 반환 , 반환 타입은 ENUM 타입
        // A가 ENUM 상수에 없는 대상이라면 IllegalArgumentException에러 발생
        String input = "DIAMOND";
        Grade gold = Grade.valueOf(input);
        Grade gold2 = Grade.valueOf("VIP");
        System.out.println("gold = " + gold); // gold = DIAMOND
        System.out.println("gold2 = " + gold2); // 런타임 에러 발생 
    }
}
  • values() : 모든 ENUM 상수를 포함하는 배열을 반환
  • valueOf(String name) : 주어진 이름과 일치하는 ENUM 상수를 반환
  • name() : ENUM 상수의 이름을 문자열로 반환하다.
  • ordinal() : ENUM 상수의 선언 순서를 반환한다.
  • toString() : ENUM 상수의 이름을 문자열로 반환 (오버라이딩을 통해 반환 내용 변경 가능)

열거형 리팩토링

◻️ ClassGrade 리팩토링

BASIC,GOLD,DIAMOND등급에따라 할인율을 적용한 로직을 다시 살펴보자.

public class DiscountService {
    public int discount(Grade classGrade, int price) {
        int discountPercent = 0;

        if (classGrade == BASIC) { // classGrade == x001
            discountPercent = 10;
        } else if (classGrade == GOLD) { 
            discountPercent = 20;
        } else if (classGrade == DIAMOND) {
            discountPercent = 30;
        } else {
            System.out.println("할인X");
        }
        return price * discountPercent / 100;
    }
}

로직에서는 if-else를 통해 각 등급마다 할인율을 적용하는 것을 알 수 있다. 즉, 결국 등급에 의해 할인율이 결정되는 것이기 때문에 이를 등급 class에서 관리해보자.

public class ClassGrade {

    public static final ClassGrade BASIC = new ClassGrade(10);
    public static final ClassGrade GOLD = new ClassGrade(20);
    public static final ClassGrade DIAMOND = new ClassGrade(30);

    private final int discountRate;

    private ClassGrade(int discountRate) {
        this.discountRate = discountRate;
    }

    public int getDiscountRate() {
        return discountRate;
    }
}
  • discountRateprivate생성자의 매개변수를 통해 받아 객체 내의 필드에 저장하도록 한다
  • getDiscountRate를 통해 외부에서 할인율을 조회할 수 있는 기능을 추가한다.

이렇게 된다면 ClassGrade내에서 사전에 정의한 BASIC,GOLD,DIAMOND등급은 각각 10,20,30의 할인율을 갖게되고 할인율 조회도 가능해진다.

ClassGrade.BASIC.getDiscount() // 10

ClassGrade에서 등급마다 할인율을 관리하게 된다면 DiscountService로직은 더 이상 if-else의 분기를 통해 할인율을 관리할 필요가 없어진다.

public class DiscountService {

    public int discount(ClassGrade classGrade, int price) {
        return price * classGrade.getDiscountRate() / 100;
    }
}

리팩토링을 마치고 DiscountService 로직을 보니 단순한 할인율 계산을 하는 로직만 남았다. ClassGrade에서 각 등급마다 할인율을 갖도록 리팩토링 하였는데 추가로 각 등급마다 할인율을 계산하여 갖고 있게 할 수 없을까? 최종적으로 리팩토링을 진행하면 다음과 같다.

public class ClassGrade {

    public static final ClassGrade BASIC = new ClassGrade(10);
    public static final ClassGrade GOLD = new ClassGrade(20);
    public static final ClassGrade DIAMOND = new ClassGrade(30);

    private final int discountRate;

    private ClassGrade(int discountRate) {
        this.discountRate = discountRate;
    }

    public int getDiscountRate() {
        return discountRate;
    }

    public int discount(int price) {
        return price * discountRate / 100;
    }

}

◻️ ENUM 리팩토링

위의 ClassGrade와 동일한 방향으로 ENUM을 리팩토링 해보자.

public enum Grade {

    BASIC(10), // private static final Grade BASIC = new Grade(10);
    GOLD(20),
    DIAMOND(30);

    private final int discountRate;

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

    public int getDiscountRate() {
        return discountRate;
    }
    
    public int discount(int price) {
        return price * discountRate / 100;
    }
}

즉, 더이상 DiscountService로직은 필요 없고 ENUM 열거형에서 고정된 범위의 상수를 설정하고 해당 상수와 괄련된 기능(메서드)를 제공하고 외부에서 이를 조회할 수 있게되었다.

정리

열거형 패턴 : 타입안정성데이터 일관성이 보장 된 고정된 범위의 상수 집합, 데이터 타입

  • 타입 안정성
    • private 생성자로 인한 외부에서 객체 생성 금지
    • 열거형 클래스 내에서 미리 정의된 동일 타입의 객체를 상수화하여 고정된 범위의 상수 구현
    • 상수와 관련된 기능을 객체지향 관점에서 클래스 내의 필드와 메서드를 통해 구현 가능
  • 데이터 일관성
    타입 안정성에서 발생한 제약으로인해 데이터의 일관성이 유지

0개의 댓글