[Java ☕️] ENUM :: 타입 안전성

ConewLog·2024년 8월 9일
0

Java ☕️

목록 보기
7/7

☕️ 김영한의 실전 자바 - 중급 1편 을 수강하며 학습한 내용을 저만의 언어로 정리하고 있습니다.


1. 이상한 아이스크림 주문 키오스크

아이스크림 주문 시스템

개발자 코뉴는 취업이 되지 않아서 아이스크림 가게를 차렸다. 기이한 취향으로 인해 맛은 바닐라랑 민트초코랑 와사비밖에 없다. 🤤

전공 지식을 살려 키오스크에 탑재될 주문 시스템을 본인이 코딩하기로 한다.

  • 바닐라 아이스크림을 주문하면 가격이 3000원이 나와야 하고, 메시지로 "무난한 맛"을 출력해야 한다.
  • 민트초코 아이스크림을 주문하면 가격이 5000원이 나와야 하고, 메시지로 "최고의 맛"을 출력해야 한다.
  • 와사비 아이스크림을 주문하면 가격이 4000원이 나와야 하고, 메시지로 "이달의 맛"을 출력해야 한다.

왜인지 모르겠지만 손님이 키오스크에서 직접 주문할 맛을 문자로 입력하고 전송하게 했다.

💻 코드

IceCreamOrderService

public class IceCreamOrderService {
    public String orderMessage(String flavor) {
        if (flavor.equals("VANILLA")) {
            return "무난한 맛";
        } else if (flavor.equals("MINT_CHOCO")) {
            return "최고의 맛";
        } else if (flavor.equals("WASABI")) {
            return "이달의 맛";
        } return "주문 오류";
    }

    public int orderPrice(String flavor) {
        int price = 0;
        if (flavor.equals("VANILLA")) {
            return 3000;
        } else if (flavor.equals("MINT_CHOCO")) {
            return 5000;
        } else if (flavor.equals("WASABI")) {
            return 4000;
        } return price;
    }
}

IceCreamMain

public class IceCreamMain {
    public static void main(String[] args) {
        // VANILLA, MINT_CHOCO, WASABI 아이스크림을 파는 가게가 있다.
        // 아이스크림을 주문해보자
        IceCreamOrderService orderService = new IceCreamOrderService();

        String message = orderService.orderMessage("MINT_CHOCO");
        int price = orderService.orderPrice("MINT_CHOCO");

        System.out.println("price = " + price + " message = " + message);
    }
}
  • 손님이 "MINT_CHOCO" 문자열을 전송했을 때, 결과로 price = 5000 message = 최고의 맛이 나온다. "VANILLA", "WASABI"의 경우도 원하는 결과를 잘 반환한다.
  • 이대로 가게를 오픈해도 되는걸까?

문제점

만약 손님이 "MINT_CHOCO" 대신 "TOOTHPASTE"를 입력한다면?
아니면 "MINT_CHOCO" 대신 "mint choco"를 입력한다면?

String message = orderService.orderMessage("TOOTHPASTE");
int price = orderService.orderPrice("TOOTHPASTE");

System.out.println("price = " + price + " message = " + message);
  • price = 0 message = 주문 오류 를 뱉으며 정상적으로 주문이 되지 않을 것이다.
  • 키오스크에 문자를 입력하는 것도 쓰레기고 제대로 되지도 않아서 아이스크림가게는 곧 망해버리고 말것이다.
  • 손님이 한치의 오차도 없이 우리가 정해놓은 문자열인 VANILLA, MINT_CHOCO, WASABI를 입력할거라고 보장할 수 없다.

2. 문자열 - 타입 안전성 문제

위에서 만든 IceCreamOrderService의 두 메서드 orderMessage(String flavor), orderPrice(String flavor)는 아이스크림의 맛을 문자열로 받아 주문을 처리한다.
이로 인해 오타, 혹은 다른 문자열을 입력해서 주문했을 때 주문 결과에 문제가 있었다.

문자열을 사용하는 현재 방식은 타입 안전성에 문제가 있다.

  • 값 제한 부족

    • 주문이 가능한 "VANILLA", "MINT_CHOCO", "WASABI" 문자열만 받아야 하나,

    • 오타 "VANILA", "MINT-CHOCO", "wasabi" 등이 들어와도 일단 orderService의 메서드에 넘길 수 있다.

      • 그리고 손님 입장에서는 "mint choco""mint-choco""MINT_CHOCO"나 다 같은 결과를 내지 않을까? 라는 생각을 할 수도 있다.
    • 아예 없는 값인 "chocolate", "TOOTHPASTE" 등도 일단 orderService의 메서드에 넘길 수 있다.

  • 컴파일 시 오류 감지 불가

    • 컴파일러 입장에서는 어떤 문자열이든 String이 넘어오면 문제가 없기 때문에 오류라고 판단하지 않는다.
    • 런타임이 되어서야 오류를 판단할 수 있다.

3. 타입 안전 열거형 패턴 적용

타입 안전 열거형 패턴 (Type-safe Enum Pattern)

개발자가 나열(enumeration)한 상수들을 정의하고, 이 상수들만 사용할 수 있도록 하는 패턴이다.

  • enum이 자바에 공식적으로 도입되기 전 사용하던 패턴이다.
  • 제한된 인스턴스 생성
    • 사전에 정의된 인스턴스만 생성하고, 이 인스턴스만 사용할 수 있도록 한다.
  • 타입 안전성
    • 컴파일 시점에 타입을 체크할 수 있다.

💻 타입 안전 열거형 패턴 적용 코드

⭐️ IceCreamFlavor

public class IceCreamFlavor {

    // 아이스크림 맛을 상수로 선언한다.
    public static final IceCreamFlavor VANILLA = new IceCreamFlavor();
    public static final IceCreamFlavor MINT_CHOCO = new IceCreamFlavor();
    public static final IceCreamFlavor WASABI = new IceCreamFlavor();

    // 외부에서 임의로 인스턴스를 생성할 수 없도록 private 생성자를 만든다.

    private IceCreamFlavor() {
    }
}
  • 사용할 아이스크림 맛들을 상수로 선언하고, 인스턴스를 생성한다.

  • 외부에서 새로 인스턴스를 생성할 수 없게 생성자를 private로 접근 제한한다.

    • 프로그램 전체에서 사용할 수 있는 IceCreamFlavor 상수들은 VANILLA, MINT_CHOCO, WASABI 로 제한된다.
    • IceCreamFlavor.VANILLA, MINT_CHOCO, WASABI는 항상 처음의 참조값을 유지한다. == 비교가 가능해진다.

IceCreamOrderService (수정)

public class IceCreamOrderService {
    public String orderMessage(IceCreamFlavor flavor) {
        if (flavor == IceCreamFlavor.VANILLA) {
            return "무난한 맛";
        } else if (flavor == IceCreamFlavor.MINT_CHOCO) {
            return "최고의 맛";
        } else if (flavor == IceCreamFlavor.WASABI) {
            return "이달의 맛";
        } return "주문 오류";
    }

    public int orderPrice(IceCreamFlavor flavor) {
        int price = 0;
        if (flavor == IceCreamFlavor.VANILLA) {
            return 3000;
        } else if (flavor == IceCreamFlavor.MINT_CHOCO) {
            return 5000;
        } else if (flavor == IceCreamFlavor.WASABI) {
            return 4000;
        } return price;
    }
}

IceCreamMain (수정)

public class IceCreamMain {
    public static void main(String[] args) {
        // VANILLA, MINT_CHOCO, WASABI 아이스크림을 파는 가게가 있다.
        // 아이스크림을 주문해보자
        IceCreamOrderService orderService = new IceCreamOrderService();

        String message = orderService.orderMessage(IceCreamFlavor.MINT_CHOCO);
        int price = orderService.orderPrice(IceCreamFlavor.MINT_CHOCO);

        System.out.println("price = " + price + " message = " + message);
    }
}

4. ENUM Type

위에서 설명한 타입 안전 열거형 패턴을 편리하게 사용할 수 있도록 자바 5부터 도입된 것이 Enum Type이다.

Enum type의 특징

  • 타입 안전성 제공
  • 코드 가독성 향상
  • 예상 가능한 값들의 집합 표현

💻 Enum type 적용 코드

⭐️IceCreamFlavor (enum으로 수정)

public enum IceCreamFlavor {
    VANILLA, MINT_CHOCO, WASABI
}
  • 아주아주 편리하게 타입 안전 열거형 패턴을 적용할 수 있다...! 🥹

💻 코드 리팩토링

더 나아가, enum type도 클래스이므로 기존에 IceCreamOrderService에 속해있었던 메시지나 가격 관련 정보를 IceCreamFlavor 하나에 묶어서 보관할 수 있다!

⭐️IceCreamFlavor (리팩토링)

public enum IceCreamFlavor {
    VANILLA("무난한 맛", 3000),
    MINT_CHOCO("최고의 맛" ,5000),
    WASABI("이달의 맛", 4000);

    private final String message;
    private final int price;
	
    // private로 막혀있다고 생각하면 된다.
    IceCreamFlavor(String message, int price) {
        this.message = message;
        this.price = price;
    }

    public String getMessage() {
        return message;
    }

    public int getPrice() {
        return price;
    }
}
  • 이제 IceCreamOrderService를 삭제해도 상관 없다.

IceCreamOrderMain

import static enumeration.example.IceCreamFlavor.MINT_CHOCO;

public class IceCreamMain {
    public static void main(String[] args) {
        // VANILLA, MINT_CHOCO, WASABI 아이스크림을 파는 가게가 있다.
        // 아이스크림을 주문해보자
        String mintChocoMessage = MINT_CHOCO.getMessage();
        int mintChocoPrice = MINT_CHOCO.getPrice();
        System.out.println("price = " + mintChocoPrice + " message = " + mintChocoMessage);


        // 전체 주문
        System.out.println("\n전체 주문");
        IceCreamFlavor[] flavors = IceCreamFlavor.values();
        for (IceCreamFlavor flavor : flavors) {
            System.out.println("price = " + flavor.getPrice() + " message = " + flavor.getMessage());
        }
    }
}
  • 코드가 훨씬 간결해졌다!
  • 가독성도 매우매우 좋아졌다.
  • 이제 이렇게 .values()같은 enum 타입 메서드도 사용해서 IceCreamFlavor에 있는 모든 값들을 꺼내올 수도 있다.

5. 정상화된 아이스크림 주문 키오스크

아이스크림 주문 시스템 개선

enum type을 적용해서 이상한 아이스크림 주문 시스템을 개선한다면
이런 화면이 될 것이다.

  • 예상 가능한 값들의 표현: 손님은 주문할 수 있는 맛들 중 선택해서 주문이 가능하다.
  • 타입 안전성 향상: 없는 맛이나, 잘못된 값이 들어올 여지를 없애준다.

참고 사이트

profile
코뉴로그

0개의 댓글