관리 메뉴

개발공부기록

자바로 키오스크 만들기 - 필수 기능 구현(요구사항 의도를 파악하여 개발하기, 객체지향 설계를 적용하기 위한 클래스의 단계적 분할, 캡슐화) 개발 본문

프로젝트 회고/토이프로젝트

자바로 키오스크 만들기 - 필수 기능 구현(요구사항 의도를 파악하여 개발하기, 객체지향 설계를 적용하기 위한 클래스의 단계적 분할, 캡슐화) 개발

소소한나구리 2025. 3. 10. 20:19
728x90

키오스크 - 필수 기능 구현

LV1 - 기본적인 키오스크를 프로그래밍 해보기

요구 사항

LV1 은 Scanner를 활용한 입력 처리와  조건문, 반복문을 활용한 간단한 흐름 제어를 복습하여 데이터를 처리하는 방법을 강화하기 위한 학습임

더보기

햄버거 메뉴 및 출력 선택하기 

  • 실행 시 햄버거 메뉴가 표시되고, Scanner로 숫자를 입력 받아서 메뉴를 선택할 수 있음
  • 제시된 메뉴 중 입력받은 숫자에 따라 다른 로직을 실행하는 코드를 작성
  • 반복문을 이용해서 특정 번호가 입력되면 프로그램을 종료

실행 예시

LV1 구현

Main 클래스 생성

package required;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            int input = inputValidator(scanner);
            if (input == 0) {
                System.out.println("프로그램을 종료합니다.");
                break;
            }
            if (input == -9999) {
                continue;
            }

            selectMenu(input);
            System.out.println("주문이 완료 되었습니다.");
            break;
        }
    }
    
    // 여러 메서드들
}
  • Main 클래스의 main 메서드 하나로 LV1의 요구사항을 만족하는 프로그램을 작성했다.
  • 무한 반복문을 while문으로 작성하여 inputMenu() 메서드를 통해 입력받은 값을 한번 타입 검증을 하여 반환하고 이후 반환된 값을 각 조건에 따라 동작하도록 로직을 작성했다.
  • 가급적 기능들을 메서드로 추출하여 실제 프로그램이 실행되는 로직을 메서드 이름으로 구분하고 최대한 가독성을 좋게 하도록 구성하였다
  • 그러나 프로그램을 종료하고, 타입이 잘못 입력 되었을 때 다시 반복문을 실행하는 로직은 지금 상태에선 개선하기가 어려워서 그대로 두었다.
  • 최종적으로 선택이 완료되면 주문이 완료 되었다는 문구를 출력하고 프로그램을 종료한다.

menuPrinter()

private static void menuPrinter() {
    System.out.println("[SHAKESHACK MENU]");
    System.out.println("1. ShackBurger\t| ₩6,900 | 토마토, 양상추, 쉑소스가 토핑된 치즈버거");
    System.out.println("2. SmokeShack\t| ₩8,900 | 베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거");
    System.out.println("3. CheeseBurger\t| ₩6,900 | 포테이토 번과 비프패티, 치즈가 토핑된 치즈버거");
    System.out.println("4. Hamburger\t| ₩5,900 | 비프패티를 기반으로 야채가 들어간 기본버거");
    System.out.println("0. 종료\t\t\t| 종료");
}
  • 처음 프로그램을 실행하면 반복적으로 작성된 메뉴를 출력하는 메서드이다
  • 의미없는 출력문을 프로그램 실행 로직에 포함하기보다 별도의 메서드로 묶어서 메서드 이름 파악하는 것이 가독성을 향상 시키는데 도움이 될 것이라고 생각했다.

inputValidator()

private static int inputValidator(Scanner scanner) {
    try {
        menuPrinter();
        int input = scanner.nextInt();
        scanner.nextLine();
        return Math.abs(input);
    } catch (InputMismatchException e) {
        System.out.println("숫자만 입력 가능합니다. MENU의 숫자만 입력 해주세요.");
        System.out.println();
        scanner.nextLine();
        return -9999;   // 재시작을 위한 임의 넘버 지정
    }
}
  • 입력값을 숫자만 입력받기 위해 한번 검증하는 로직을 메서드로 추출하였다.
  • menuPrinter()메서드를 호출하며 메뉴를 출력하고 숫자를 입력하면 무조건 양의 정수로 반환될 수 있도록 Math.abs(input)을 사용하였다
    • 일부러 혹은 실수로 음수를 입력하여도 메뉴가 정상적으로 선택되도록 입력값을 검증하는 메서드에서 해당 예외도 처리는 것이 맞다고 생각하였다.
  • nextInt()로 입력받기 때문에 다른 타입이 들어오면 InputMismatchException 예외가 발생하는 것을 이용하여 다른 타입이 입력값으로 들어올 경우 안내 문구를 출력하고 -9999라는 임의의 넘버를 반환함으로써 해당 값으로 조건문을 사용하여 다시 처음부터 시작할 수 있도록 로직을 구성하였다.
  • 여기서 nextInt()로 입력받은 후 scanner.nextLine()을 통해 버퍼에 남아있는 개행문자를 제거해주었다.

selectMenu()

private static void selectMenu(int input) {
    switch (input) {
        case 1 -> System.out.println("ShackBurger 1개를 선택 하셨습니다. 가격: 6,900원");
        case 2 -> System.out.println("SmokeShack 1개를 선택 하셨습니다. 가격: 8,900원");
        case 3 -> System.out.println("CheeseBurger 1개를 선택 하셨습니다. 가격: 6,900원");
        case 4 -> System.out.println("Hamburger 1개를 선택 하셨습니다. 가격: 5,400원");
    }
}
  • 검증된 입력값을 활용하여 입력값에 따라 출력되는 선택된 메뉴와 가격을 출력해주는 로직이 담긴 메서드이다.
  • 입력값은 단순한 정수이므로 스위치문을 활용하였고, 각 케이스별로 실행되는 로직이 하나이므로 Java12에 도입된 개선된 Switch Expressions 표현식을 활용하여 로직을 구성하였다.
  • Switch Expressions(개선된 Switch문)은 단일 값일 경우 화살표를 활용하여 값을 기술하면되고 중괄호를 사용할 경우 yield(Java 13부터 사용가능)키워드로 값을 지정하면 된다.
  • https://docs.oracle.com/en/java/javase/17/language/switch-expressions-and-statements.html 사용법 참고

실행 결과

더보기
  • 0을 입력하면 프로그램이 종료된다

 

  • 메뉴판에 출력된 메뉴를 선택하면 선택한 메뉴가 주문되었다는 문구가 출력된다

 

  • 메뉴판의 숫자가 아닌 문자열 등의 다른 타입이 입력되면 숫자만 입력 가능하다는 안내 문구과 함께 처음으로 돌아가서 메뉴가 출력되고 다시 입력할 수 있도록 처리했다

 

  • 메뉴판을 선택하기 위한 숫자는 0을 포함한 양의 정수이지만 음수를 입력하여도 양의 정수로 변환이 되어 메뉴가 선택되는 예외처리도 정상적으로 동작하는 것을 확인할 수 있다.

LV2 - 객체 지향 설계를 적용해 햄버거 메뉴를 클래스로 관리하기

요구 사항

더보기

요구 사항

  • 객체 지향 개념을 학습하고 데이터를 구조적으로 관리하며 프로그램을 설계 하기위해 햄버거 메뉴를 MenuItem 클래스와 List를 통해 관리하기
  • MenuItem 클래스는 이름, 가격, 설명 필드를 가지고 있음
  • main 메서드에서 MenuItem 클래스를 활용하여 반복문을 활용하여 menuItems를 탐색하면서 햄버거 메뉴를 출력해야 함

LV2 구현

MenuItem 클래스 생성

package required.lv2;

public class MenuItem {

    String name;
    long price;
    String description;


    public MenuItem(String name, long price, String description) {
        this.name = name;
        this.price = price;
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public long getPrice() {
        return price;
    }

    public String getDescription() {
        return description;
    }
}

요구사항에 따라 상품을 저장하기 위한 MenuItem을 생성하고 이름, 가격, 설명을 가지고 있으며 이후 캡슐화를 위해 생성자를 통해서 값을 초기화 한다.

지금 상태에서는 캡슐화가 되어있지 않아서 필드에서 직접 값을 꺼낼 수 있지만 아무리 그래도 필드에 직접 접근해서 값을 꺼내는건 양심이 허락하지 않기도 하고 이후 캡슐화 진행을 위해 게터를 생성하여 이 메서드를 통해 값을 사용했다.

햄버거 가게에서 단품 가격이 2억을 넘길 수 없다는걸 알지만...... 혹시모를 엄청난 슈퍼 인플레이션이 발생할 수도 있고(짐바브웨..?) 요즘 컴퓨터가 좋아졌기 때문에 가급적 안정성을 위해 가격의 타입을 long으로 작성했다.

 

Lv2 - Main 클래스의 main 메서드

public class Main {
    public static void main(String[] args) {
        List<MenuItem> menuItems = new ArrayList<>();
        
        // MenuItem 생성 및 List에 메뉴 아이템 추가
        menuItems.add(new MenuItem("ShackBurger", 6_900, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"));
        menuItems.add(new MenuItem("SmokeShack", 8_900, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"));
        menuItems.add(new MenuItem("CheeseBurger", 6_900, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"));
        menuItems.add(new MenuItem("Hamburger", 5_900, "비프패티를 기반으로 야채가 들어간 기본버거"));
  • 생성한 아이템을 관리하기 위해 List<MenuItem> menuItems = new ArrayList<>()로 구현체를 ArrayList를 사용했다.
  • 그 후 add()메서드를 통해 new MenuItem()으로 아이템을 하나씩 저장해주었다.

menuPrinter(menuItems)

public static void main(String[] args) {
  
  // ... List생성 및 아이템 추가 코드
  
  while (true) {
        menuPrinter(menuItems);
        int input = inputValidator(scanner);
        if (input == 0) {
            System.out.println("프로그램을 종료합니다.");
            break;
        }
        if (input == -9999) {
            continue;
        }

        selectMenu(input);
        System.out.println("주문이 완료 되었습니다.");
        break;
    }
}

private static void menuPrinter(List<MenuItem> menuItems) {
    System.out.println("[SHAKESHACK MENU]");
    int count = 1;
    for (MenuItem menuItem : menuItems) {
        System.out.println(count++ + ". " + menuItem.getName() + "\t| ₩ " + menuItem.getPrice() + "\t| " + menuItem.getDescription());
    }
    System.out.println("0. 종료\t\t\t| 종료");
}
  • 기존에는 inputValidator()코드 내부에서 menuPrinter() 메서드를 호출하여 메뉴를 출력했다
  • 하지만 메뉴를 관리하는 menuItems를 활용하여 메뉴를 출력 해야하기 때문에 menuPrinter()메서드의 호출 시점을 애플리케이션이 처음 반복문이 실행되자 마자 출력되도록 변경하였다.
  • 처음에는 LV1 구조 그대로 menuItems를 활용해보기 코드를 작성했으나 inputValidator(scanner, menuItems)메서드에서 인자로 받은 menuItems를 menuPrinter(menuItems) 메서드에게 인자로 전달하는 불필요한 구조가 되는 것을 확인하고 지금과 같이 리팩토링을 했다.
  • 리펙토링된 구조에서 menuPrinter(menuItems) 메서드는 인자로 전달 받은 리스트를 통해 메뉴의 개수, 이름, 가격, 정보를 반복문으로 출력하고 메뉴를 전부 출력하면 종료 버튼 안내도 출력한다.
  • 애플리케이션의 실행결과는 기존과 동일하다.

LV3 - 객체 지향 설계를 적용해 순서 제어를 클래스로 관리하기

요구 사항

더보기

요구 사항

  • main 함수에서 관리하던 전체 순서 제어를 Kiosk 클래스를 통해 관리하도록 변경
    • kiosk 클래스는 키오스크 프로그램의 메뉴를 관리하고 사용자 입력을 처리해야함
    • MenuItem을 관리하는 리스트가 필드로 존재하고 main 함수에서 관리하던 입력과 반복문 로직을 start() 메서드를 만들어서 관리해야 함
    • MenuItem을 관리하는 리스트는 Kiosk 클래스 생성자를 통해 값을 할당해야 함, 즉 Kiosk 객체를 생성하고 사용하는 main 함수에서 객체를 생성할 때 값을 넘겨주어야 함
  • 기존 요구사항은 그대로 가져가도록 검토가 필요함
    • 콘솔에 햄버거 메뉴를 출력
    • 사용자의 입력을 받아 메뉴를 선택하거나 프로그램을 종료
    • 유효하지 않은 입력에 대해 오류 메시지를 출력
    • 0을 입력하면 프로그램이 '뒤로가기' 되거나 '종료' -> 여기서는 종료로 구현

LV3 구현

Kiosk 클래스 생성

package required.lv3;

public class Kiosk {
    List<MenuItem> menuItems = new ArrayList<>();

    /**
     * 리스트인 menuItems 의 값을 Kiosk를 생성할 때 초기화하는 생성자
     * Collections.addAll(this.menuItems, menuItems); 메서드를 사용할 수 있지만 이해하기 쉽게 이대로 유지
     * @param menuItems MenuItem을 가변으로 받을 수 있도록 ... 키워드를 사용
     */
    public Kiosk(MenuItem ...menuItems) {
        for (MenuItem menuItem : menuItems) {
            this.menuItems.add(menuItem);
        }
    }
    // 기존 Main클래스에서 동작하던 메서드들을 static 메서드가 아닌 일반 메서드로 변경
}
  • 기존에 Main 클래스의 로직을 대거 Kiosk 클래스로 이동 시켜서 애플리케이션의 흐름을 Kiosk에서 관장한다.
  • 필드에 MenuItem들을 관리하는 menuItems 리스트를 가지고 있으며 Kiosk가 생성될 때 MenuItem들을 저장하기 위해 생성자의 매개변수를 MenuItem을 받도록 했다.
  • 이때 한개가 들어올 수도 있고 여러개가 들어올 수 있기 때문에 가변 인자를 사용했으며, 들어온 데이터를 반복문을 통해서 menuItems.add() 메서드를 통해 리스트에 하나씩 담아서 보관한다.
  • 여기서 Collections.addAll(this.menuItems, menuItems)를 사용하도록 리펙토링 해도 되지만 Collections는 익숙하지 않아서 좀 더 원초적인 코드로 일단 두었지만 실무에서는 IDE가 지원하는 리펙토링을 적극 활용할 것이기에 Collections.addAll()을 활용할 것 같다.
  • 추가적으로 LV2에서 static 메서드로 동작하던 inputValidator(), menuPrinter(), selectMenu() 메서드들을 static 메서드에서 인스턴스 메서드로 수정하여 Kiosk 인스턴스가 관리하도록 수정했다.

start() 메서드

public void start() {
    Scanner scanner = new Scanner(System.in);

    while (true) {
        menuPrinter(menuItems);
        int input = inputValidator(scanner);
        if (input == 0) {
            System.out.println("프로그램을 종료합니다.");
            break;
        }
        if (input == -9999) {
            continue;
        }

        selectMenu(input);
        System.out.println("주문이 완료 되었습니다.");
        break;
    }
}

 

  • LV2버전 Main클래스의 main 메서드에서 관리하던 프로그램 실행 흐름 로직을 start() 메서드가 관리하도록 변경했다
  • 코드 자체가 변화된 것은 없고 깔끔하게 Scanner를 생성하고 반복문으로 프로그램 실행에 관한 메서드들을 호출하여 흐름을 관리한다.

LV3 Main 클래스

package required.lv3;

public class Main {

    public static void main(String[] args) {
        // Kiosk를 생성할 때 MenuItem 세팅
        Kiosk kiosk = new Kiosk(
                new MenuItem("ShackBurger", 6_900, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"),
                new MenuItem("SmokeShack", 8_900, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"),
                new MenuItem("CheeseBurger", 6_900, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"),
                new MenuItem("Hamburger", 5_900, "비프패티를 기반으로 야채가 들어간 기본버거")
        );

        kiosk.start();
    }
}
  • 변경된 LV3의 Main클래스의 main메서드에는 단지 new Kiosk()로 키오스크를 생성하고 kiosk.start()로 프로그램을 실행하는 로직만 존재하도록 매우 깔끔해져 조금씩 클래스의 기능이 분리되어 객체 지향 적인 설계가 되어가고 있다.
  • 여기에서 Kiosk를 생성할 때 new MenuItem()으로 아이템을 원하는 만큼 생성해서 넘겨줄 수 있다
  • 실행 시 프로그램은 LV1과 동일하게 동작한다.

LV4 - 객체 지향 설계를 적용해 음식 메뉴와 주문 내역을 클래스 기반으로 관리하기

요구 사항

더보기

요구 사항

  • MenuItem 클래스를 관리하는 Menu 클래스를 생성
    • 예를 들어 버거 메뉴, 음료 메뉴 등 각 카테고리 내에 여러 MenuItem을 포함함
  • List<MenuItem>은 Kiosk 클래스가 관리하기에 적절하지 않기 때문에 Menu 클래스가 관리하도록 변경
  • Menu 클래스에는 여러 버거들을 포함하는 상위 개념 '버거' 같은 카테고리 이름 필드를 갖으며 메뉴 카테고리 이름을 반환하는 메서드가 구현되어야 함

 

실행 예시

LV4 구현

Menu 클래스

package required.lv4;

public class Menu {

    List<MenuItem> menuItems = new ArrayList<>();

    public Menu(String category) {
        this.category = category;
    }

    String category;

    public String getCategory() {
        return category;
    }

    public void add(MenuItem ...menuItems) {
        Collections.addAll(this.menuItems, menuItems);
    }

    public MenuItem findMenuItem(int input) {
        return menuItems.get(input - 1);
    }

    public List<MenuItem> findAllMenuItem() {
        return menuItems;
    }

}
  • MenuItem의 대분류인 카테고리를 나타내는 Menu 클래스이다
  • 요구사항에 따라 필드로 LV3에서 관리하던 List<MenuItem> menuItems가 Menu 클래스로 이동하였고 menuItems의 카테고리를 나타내는 String category를 가지고 있따
  • Menu를 생성할 때 category의 값을 초기화하여 필드로 가지고 있는 menuItems들의 카테고리 명을 지정할 수 있다

add(MenuItem ...menuItems)

  • LV3의 Kiosk 클래스에서는 Kiosk를 생성할 때 MenuItem들을 생성하여 값을 초기화 했지만 여기서는 add(...menuItems) 메서드로 각 Menu 클래스의 카테고리별로 MenuItem을 생성하여 초기화 할 수 있도록 수정했다
  • 마찬가지로 메뉴별로 여러 종류의 MenuItem을 생성하여 등록할 수 있도록 가변인자를 매개변수로 사용하였고 collections.addAll()메서드를 사용하여 넘어온 menuItems들을 menuItems 리스트에 저장한다

findMenuItem(int input)

  • 검증된 입력값을 매개변수로 받은 다음 -1 연산을 통해 menuItems.get()메서드로 찾고자하는 MenuItem을 반환한다
  • -1 연산을 하지 않으면 List의 index는 0부터 시작하기 때문에 제대로 조회할 수 없다

findAllMenuItem()

  • menuItems 리스트 자체를 반환한다.

Kiosk 클래스

package required.lv4;

public class Kiosk {

    List<Menu> menus = new ArrayList<>();

    public Kiosk(Menu  ...menus) {
        Collections.addAll(this.menus, menus);
    }

    // 메서드들 따로 설명
}
  • 전체 애플리케이션의 흐름을 관리하고 Main에서 생성한 Menu들을 List로 관리한다.
  • Kiosk를 생성할 때 Main에서 생성한 menu들을 인자로 입력하여 List에 값을 초기화 하며 이때도 Collections.addAll()을 사용했다.
public void start() {
    Scanner scanner = new Scanner(System.in);

    while (true) {
        categoryPrinter(menus);                         // 메인 메뉴 -> 메뉴의 대분류(카테고리) 출력
        int mainInput = inputValidator(scanner);        // 입력값 검증 -> 카테고리 입력

        if (mainInput == 0) {
            System.out.println("프로그램을 종료합니다.");
            break;
        }

        if (menus.size() < mainInput) {
            System.out.println("잘못된 메뉴를 선택하였습니다 다시 입력해 주세요.");
            continue;
        }

        Menu menu = selectMainMenu(mainInput);

        while (true) {
            menuPrinter(menu);                          // 카테고리 하위 메뉴 출력
            int menuInput = inputValidator(scanner);    // 입력값 검증(재사용) -> 상세 메뉴 입력

            if (menuInput == 0) {
                System.out.println("처음으로 이동합니다.");
                break;
            }

            if (menu.findAllMenuItem().size() < menuInput) {
                System.out.println("잘못된 메뉴를 선택하였습니다 다시 입력해 주세요.");
                continue;
            }

            selectMenuItem(menuInput, menu);            // 선택된 메뉴 출력
            System.out.println("주문이 완료 되었습니다.");
            System.out.println();
            break;
        }
    }
}

start()

  • 전체 흐름은 LV3와 비슷하지만 2중 반복문을 사용하면서 먼저 카테고리를 입력하고 그 카테고리 안의 상세 메뉴를 선택하도록 변경하였다
  • 요구사항에 따라 카테고리 선택 시에는 0을 누르면 종료되지만 상세 메뉴를 선택시 0을 누르면 처음으로 돌아가도록 검증 로직을 작성하였다.
  • 그리고 메서드가 추가됨에 따라 Kiosk와 Menu가 가지고있는 List의 size보다 입력값이 크면 예외가 발생하기 때문에 이를 안내 문구와 함께 다시 값을 입력하도록 예외 처리를 하였다.
  • 흐름은 아래와 같다
    • 1. 카테고리 메뉴 출력
    • 2. 입력값 검증 메서드를 통해 값을 검증하고 0이 입력되면 프로그램을 종료
    • 3. 모든 검증이 끝나면 Menu를 조회해서 menu를 꺼내고 다음 반복문에 진입
    • 4. menus의 size()보다 큰 값이 입력되면 잘못 입력한 것이므로 다시 입력받도록 continue를 진행
    • 5. menu를 인자로 받아서 menu하위의 상세 메뉴를 출력
    • 6. 입력값 검증 메서드를 통해 값을 검증하고 0이 입력되면 프로그램을 처음부터 다시 시작
    • 7. menu.findAllMenuItem().size()보다 큰 값이 입력되면 잘못 입력한 것이므로 다시 입력받도록 continue를 진행
    • 8. 모든 검증이 끝나면 선택된 아이템을 출력하고 주문이 완료되었다는 문구를 출력하고 종료함
private void categoryPrinter(List<Menu> menus) {
    System.out.println("[MAIN MENU]");
    int count = 1;
    for (Menu menu : menus) {
        System.out.println(count++ + ". " + menu.getCategory());
    }
    System.out.println("0. 종료");
}

private Menu selectMainMenu(int input) {
    return menus.get(input - 1);
}

private int inputValidator(Scanner scanner) {
    try {
        int input = scanner.nextInt();
        scanner.nextLine();
        return Math.abs(input);
    } catch (InputMismatchException e) {
        System.out.println("숫자만 입력 가능합니다. 숫자만 입력 해주세요.");   // 숫자만 입력 가능하다고 수정(재사용 위함)
        System.out.println();
        scanner.nextLine();
        return 0;           // 0으로 반환 Menu
    }
}

categoryPrinter(List<Menu> menus)

  • 카테고리들을 출력하는 메서드이다
  • List인 menus를 인자로 받아서 반복문으로 순회하며 getcategory()메서드를 통해 메인 메뉴(카테고리)들을 출력한다
  • 종료 버튼도 함께 안내한다

selectMainMenu(int input)

  • 타입 검증을 끝낸 입력값을 통해 선택한 메뉴를 조회하기 위한 메서드이다
  • 마찬가지로 input값에 -1을 조회해야 menus.get()이 정상적으로 동작하여 조회된다

inputValidator(Scanner scanner)

  • 입력값을 타입 검증을 하는 로직으로 기존 LV1, LV2와 로직은 동일하다
  • 다만, 재사용을 위해 출력문을 숫자만 입력해 달라고 안내문을 변경하였고 재시작을 위한 임의의 return값이 아니라 0을 return 하여 MainMenu를 입력시에는 프로그램이 종료되고, 상세 메뉴를 입력시에는 프로그램이 처음으로 돌아가도록 로직을 수정했다.
private void menuPrinter(Menu menu) {
    List<MenuItem> menuItems = menu.findAllMenuItem();

    System.out.println("[" + menu.getCategory() + " MENU]");

    int count = 1;
    for (MenuItem menuItem : menuItems) {
        System.out.printf("%d. %-18s | ₩ %d | %s%n", count++, menuItem.getName(), menuItem.getPrice(), menuItem.getDescription());        }
    System.out.println("0. 처음으로             | 처음으로 이동");

}

private void selectMenuItem(int input, Menu menu) {
    MenuItem menuItem = menu.findMenuItem(input);
    System.out.printf("선택한 메뉴: %s | ₩ %d | %s%n", menuItem.getName(), menuItem.getPrice(), menuItem.getDescription());
}

menuPrinter(Menu menu)

  • menu를 입력 받아서 menu가 가진 상세 아이템들을 출력해주는 메서드이다.
  • menu의 category필드로 카테고리의 메뉴임을 알리고 마찬가지로 반복문으로 순회하면서 menu의 상세 메뉴들을 출력한다.
  • 메뉴들을 추가적으로 입력하면서 MenuItem의 이름이 긴 상품이 등록되어 \t보다 더 명확하게 칸을 구분하기 위해 printf문과 %포맷팅을 사용하여 출력했다.

selectMenuItem(int input, Menu menu)

  • 최종 검증된 입력값과 menu를 매개변수로 받아 input값으로 menu.findMenuItem(input)으로 사용자가 선택한 최종 MenuItem을 출력해준다.

Main 클래스

package required.lv4;

public class Main {
    public static void main(String[] args) {

        /**
         * Menu를 생성하며 대분류 이름을 지정
         */
        Menu burgers = new Menu("Burgers");     // 버거 메뉴 생성
        Menu drinks = new Menu("Drinks");       // 음료 메뉴 생성
        Menu desserts = new Menu("Desserts");   // 간식 메뉴 생성

        /**
         * 생성한 Menu의 카테고리에 따라 상세 메뉴(MenuItem)들을 생성하여 저장
         */
        burgers.add(
                new MenuItem("ShackBurger", 8_900, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"),
                new MenuItem("SmokeShack", 11_100, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"),
                new MenuItem("ShackMeisterBurger", 9_900, "크리스피 어니언과 치즈 패치, 쉑 소스가 기반인 아메리칸 스타일의 버거"),
                new MenuItem("Hamburger", 7_300, "비프패티를 기반으로 야채가 들어간 기본버거"),
                new MenuItem("ShroomBurger", 10_500, "치즈로 속을 채워 바삭하게 튀겨낸 포토벨로 버섯 패티가 기반인 베지테리안 버거")
        );
        drinks.add(
                new MenuItem("Lemonade", 4_500, "매장에서 직접 만드는 상큼한 레몬에이드"),
                new MenuItem("Fifty/Fifty", 4_000, "레몬에이드와 유기농 홍차를 우려낸 아이스 티가 만나 탄생한 쉐이크쉑의 시크니처 음료"),
                new MenuItem("Fountain Soda", 2_900, "코카콜라, 코카콜라 제로, 스프라이트, 환타(오렌지, 그레이프, 파인애플)"),
                new MenuItem("Classic Shakes", 6_800, "쫀득하고 진한 커스더드가 들어간 클래신 쉐이크(바닐라/초콜릿/스트로베리/블랙 & 화이트/솔티트 카라멜/ 피넛 버터/커피")
        );
        desserts.add(
                new MenuItem("HotDog", 5_100, "훈연한 비프 소지지와 포테이토 번을 사용한 핫 도그"),
                new MenuItem("Fries", 4_900, "바삭하고 담백한 크릴클 컷 프라이"),
                new MenuItem("CheeseFries", 6_000, "고소하고 진한 치즈 소스를 듬뿍 올린 크링클 컷 프라이")
        );

        /**
         * 키오스크 생성시 생성된 Menu를 키오스크에 등록
         */
        Kiosk kiosk = new Kiosk(burgers, drinks, desserts);
        kiosk.start();
    }
}
  • MenuItem을 생성할 Menu들을 각각 생성하고, 생성할 때 category 필드의 값을 초기화 하여 어떤 카테고리인지 구분한다.
  • 이후 생성된 각 Menu들의 add메서드를 통해 new MenuItem()으로 각 카테고리에 맞는 MenuItem을 초기화 한다.
    • add()메서드가 가변인자로 MenuItem을 받기 때문에 같은 add메서드임에도 오버로딩을 하지 않고 다양한 개수의 MenuItem()을 생성하여 저장할 수 있다
    • 추가된 메뉴는 쉑쉑버거 사이트에서 실제 메뉴를 골라서 입력하였고 가격도 업데이트 하였다.
  • 이후 키오스크를 실행할 때 생성한 Menu들을 인자로 넘겨서 kiosk에서 메뉴들을 보여줄 수 있도록 하고 start()함수를 호출하여 프로그램을 실행한다

실행 결과

더보기
  • 처음 프로그램을 실행하면 MAIN MENU를 선택할 수 있으며 메뉴를 선택하면 각 해당 메뉴에 맞게 이동한다.
  • 타입을 잘못입력하면 프로그램이 종료되며 메뉴에 없는 숫자를 입력하면 다시 입력을 받는다.
  • 정상적으로 0을 누르면 프로그램이 종료된다
  • 메뉴의 값을 입력하면 해당 카테고리의 상세 메뉴로 진입한다
  • 상세 메뉴에서 0번을 누르면 처음으로 이동한다
  • 마찬가지로 상세 메뉴에서도 메뉴에 없는 값을 입력하면 다시 값을 입력 받게되고 타입을 잘못 입력하면 프로그램이 재시작 된다
  • 정상적인 메뉴를 선택하면 선택한 메뉴가 출력되고 주문이 완료되고 프로그램은 다시 처음으로 돌아간다

LV5 - 캡슐화 적용

요구 사항

  • MenuItem, Menu, Kiosk 클래스의 필드에 직접 접근하지 못하도록 캡슐화를 하고 Getter와 유용한 메서드를 통해 데이터를 관리

LV5 구현

Kisok, Menu, MenuItem 클래스 캡슐화 적용

public class Kiosk {

    private final List<Menu> menus = new ArrayList<>();
    // 나머지 코드 생략
}

public class Menu {

    private final List<MenuItem> menuItems = new ArrayList<>();
    private final String category;
    // 나머지 코드 생략
}

public class MenuItem {

    private final String name;
    private final long price;
    private final String description;
    // 나머지 코드 생략
}
  • 이미 나는 LV1을 구현할 때부터 접근제어자를 통해 캡슐화를 하지 않았어도 이미 메서드를 통해서 값을 조회하고 생성자를 통해서만 값을 초기화했으므로 캡슐화의 기반들 다져놓으면서 작업을 했다
  • 이미 이렇게 습관처럼 하고 있기 때문에 모든 클래스의 각 필드에 private final 을 통해 접근제어자를 입력하여도 모든 코드의 수정없이 캡슐화가 적용되어 에러가 발생하지 않고 코드가 동작한다.
  • 또한 해당 클래스에서만 사용하는 메서드는 private 메서드로 이미 코드를 작성했기 때문에 다른 클래스에서는 public 메서드들 중에서만 골라서 사용할 수 있다
  • 실행 결과는 LV5와 같다
728x90