Dev Projects

[Java Project_키오스크 과제] 필수 기능 구현 및 단계별 설계

기만나🐸 2025. 1. 15. 16:28

개요

CH 2 키오스크 과제를 진행하며 level별로 구현한 방법에 대해서 작성해보겠습니다 🤓

 

📌 키오스크 과제 필수 기능 가이드

더보기

클래스 정의

  • Main : 시작 지점이 되는 클래스, public static void main(String[] args)
  • MenuItem: 세부 메뉴 속성 가지는 클래스
    • 햄버거의 이름, 가격설명
      • 예시: ShackBurger, 6.9, 토마토, 양상추, 쉑소스가 토핑된 치즈버거
      • new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거")
  • Kiosk: 프로그램 순서 및 흐름 제어를 담당하는 클래스
  • Menu: MenuItem 클래스를 관리하는 클래스
    • 예시 : 버거 메뉴, 음료 메뉴 등 각 카테고리 내에 여러 MenuItem을 포함합니다.

Lv 1. 기본적인 키오스크를 프로그래밍해보자

  • 요구사항이 가지는 의도
    • 입력 처리와 간단한 흐름 제어를 복습합니다. (프로그래밍 검증**)**
    • Scanner 활용법, 조건문, 반복문을 재확인하며 입력 데이터를 처리하는 방법 강화
  • 햄버거 메뉴 출력 및 선택하기
    • Scanner를 사용하여 여러 햄버거 메뉴를 출력합니다.
    • 제시된 메뉴 중 입력받은 숫자에 따라 다른 로직을 실행하는 코드를 작성합니다.
    • 반복문을 이용해서 특정 번호가 입력되면 프로그램을 종료합니다.
  • Lv1을 구현하면 터미널에 이렇게 보여집니다.

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

  • 요구사항이 가지는 의도
    • 객체 지향 개념을 학습하고, 데이터를 구조적으로 관리하며 프로그램을 설계하는 방법을 익힙니다.
    • 햄버거 메뉴를 MenuItem 클래스와 List를 통해 관리합니다.
  • MenuItem 클래스 생성하기
    • 설명 : 개별 음식 항목을 관리하는 클래스입니다. 현재는 햄버거만 관리합니다.
    • 클래스는 이름, 가격, 설명 필드를 갖습니다.
  • main 함수에서 MenuItem 클래스를 활용하여 햄버거 메뉴를 출력합니다.
    • MenuItem 객체 생성을 통해 이름, 가격, 설명을 세팅합니다.
      • 키워드: new
    • List를 선언하여 여러 MenuItem을 추가합니다.
      • List<MenuItem> menuItems = new ArrayList<>();
    • 반복문을 활용해 menuItems를 탐색하면서 하나씩 접근합니다.
  • 구조 예시

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

  • 요구사항이 가지는 의도
    • 객체 지향 개념을 학습하고, 데이터를 구조적으로 관리하며 프로그램을 설계하는 방법을 익힙니다.
    • main 함수에서 관리하던 전체 순서 제어를 Kiosk 클래스를 통해 관리합니다.
  • Kiosk 클래스 생성하기
    • 설명: 키오스크 프로그램의 메뉴를 관리하고 사용자 입력을 처리하는 클래스입니다.
    • MenuItem을 관리하는 리스트가 필드로 존재합니다.
    • main 함수에서 관리하던 입력과 반복문 로직은 이제 start 함수를 만들어 관리합니다.
    • List<MenuItem> menuItems 는 Kiosk 클래스 생성자를 통해 값을 할당합니다.
      • Kiosk 객체를 생성하고 사용하는 main 함수에서 객체를 생성할 때 값을 넘겨줍니다.
  • 요구사항에 부합하는지 검토
    • 키오스크 프로그램을 시작하는 메서드가 구현되어야 합니다.
      • 콘솔에 햄버거 메뉴를 출력합니다.
      • 사용자의 입력을 받아 메뉴를 선택하거나 프로그램을 종료합니다.
      • 유효하지 않은 입력에 대해 오류 메시지를 출력합니다.
      • 0을 입력하면 프로그램이 ‘뒤로가기’되거나 ‘종료’됩니다.

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

  • Menu 클래스 생성하기
    • 설명 : MenuItem 클래스를 관리하는 클래스입니다. 예를 들어, 버거 메뉴, 음료 메뉴 등 각 카테고리 내에 여러 MenuItem을 포함합니다.
    • List<MenuItem> 은 Kiosk 클래스가 관리하기에 적절하지 않으므로 Menu 클래스가 관리하도록 변경합니다.
    • 여러 버거들을 포함하는 상위 개념 ‘버거’ 같은 카테고리 이름 필드를 갖습니다.
    • 메뉴 카테고리 이름을 반환하는 메서드가 구현되어야 합니다.
  • 구조 예시
public static void main(String[] args) {
		// Menu 객체 생성을 통해 이름 설정
		// Menu 클래스 내 있는 List<MenuItem> 에 MenuItem 객체 생성하면서 삽입
		
		// Kiosk 객체 생성
		// Kiosk 내 시작하는 함수 호출
}

public class Kiosk {

		start() {
			// 스캐너 선언
			// 반복문 시작
			
			// List와 Menu 클래스 활용하여 상위 카테고리 메뉴 출력
			
			// 숫자 입력 받기
			
			// 입력 받은 숫자가 올바르다면 인덱스로 활용하여 List에 접근하기
				// List<Menu>에 인덱스로 접근하면 Menu만 추출할 수 있겠죠?
			
			// Menu가 가진 List<MenuItem>을 반복문을 활용하여 햄버거 메뉴 출력
			
			// 숫자 입력 받기
			// 입력 받은 숫자가 올바르다면 인덱스로 활용해서 Menu가 가지고 있는 List<MenuItem>에 접근하기
				// menu.getMenuItems().get(i); 같은 형식으로 하나씩 들어가서 얻어와야 합니다.
		}

}

public class Menu {
		// MenuItem 클래스를 List로 관리
		
		// List에 들어있는 MenuItem을 순차적으로 보여주는 함수
		// List를 리턴하는 함수
		
		// 구조에 맞게 함수를 선언해놓고 가져다 사용하세요.
}

public class MenuItem {
		// 이름, 가격, 설명 필드 선언하여 관리
		
		// 구조에 맞게 함수를 선언해놓고 가져다 사용하세요.
}
  • Lv4을 구현하면 터미널에 이렇게 보여집니다.

Lv 5. 캡슐화 적용하기

  • MenuItem, Menu 그리고 Kiosk 클래스의 필드에 직접 접근하지 못하도록 설정합니다.
  • Getter와 Setter 메서드를 사용해 데이터를 관리합니다.

 


Lv 1. 기본적인 키오스크 프로그래밍

package com.example.kiosk.level1;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 메뉴를 저장할 List
        List<List<String>> menuList = new ArrayList<>();
        menuList.add(List.of(new String[]{"ShackBurger", "W 6.9", "토마토, 양상추, 쉑소스가 토핑된 치즈버거"}));
        menuList.add(List.of(new String[]{"SmokeShack", "W 8.9", "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"}));
        menuList.add(List.of(new String[]{"Cheeseburger", "W 6.9", "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"}));
        menuList.add(List.of(new String[]{"Hamburger", "W 5.4", "비프패티를 기반으로 야채가 들어간 기본버거"}));

        while (true) {
            // 전체 메뉴 출력
            printMenuList(menuList);

            // Scanner를 사용하여 여러 햄버거 메뉴를 출력합니다.
            String selectedMenu = sc.next();
            try {
                // 반복문을 이용해서 특정 번호가 입력되면 프로그램을 종료합니다.
                if ("0".equals(selectedMenu)) break;

                // 입력받은 매뉴를 정수로 변환
                int menuNumber = Integer.parseInt(selectedMenu);
                
                // 메뉴 리스트에 있는 번호라면 해당 메뉴의 이름,가격,설명을 출력 
                if (menuNumber > 0 && menuNumber <= menuList.size()) {
                    printMenu(menuList.get(menuNumber - 1));
                } else { // 아니라면 IllegalArgumentException 예외 발생
                    throw new IllegalArgumentException("입력 가능한 숫자는 0~" + menuList.size() + "입니다.");
                }
            } catch (NumberFormatException e) {
                System.out.println("\"" + selectedMenu + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
            }
            System.out.println("************************************************************************************");
        }
        System.out.println("프로그램을 종료합니다.");
    }
    public static void printMenuList(List<List<String>> menuList) {
        StringBuilder sb = new StringBuilder();
        sb.append("[ SHAKESHACK MENU ]\n");
        for (int i = 0; i < menuList.size(); i++) {
            sb.append(i + 1).append(". ");
            sb.append(menuList.get(i).get(0)).append("\t | "); // 이름
            sb.append(menuList.get(i).get(1)).append("\t | "); // 가격
            sb.append(menuList.get(i).get(2)).append("\n"); // 설명
        }
        sb.append("0. 종료      | 종료");
        System.out.println(sb);
    }
    public static void printMenu(List<String> menu) {
        String menuString = "선택한 메뉴 : " + menu.get(0) + "\t | " + menu.get(1) + "\t | " + menu.get(2) + "\n";
        System.out.print(menuString);
    }
}
  • Main.java의 `main`에서 햄버거 메뉴 출력 및 선택 기능을 구현했다.
  • 계산기 과제와 다른 점은 이번에는 Lv 1부터 예외처리를 넣었다! 🤓
    • 입력 받은 메뉴가 숫자가 아닌 경우
    • 입력 받은 메뉴가 범위를 벗어나는 경우
    • 그 외의 경우
  • `printMenuList` 메서드로 전체 메뉴가 출력되게끔 하고, `printMenu` 메서드로 선택한 메뉴를 출력하도록 했다.

 


Lv 2. 메뉴를 클래스로 관리하기

MenuItem 클래스

package com.example.kiosk.level2;

import java.util.List;

/*
* MenuItem: 세부 메뉴 속성 가지는 클래스
* 햄버거의 이름, 가격설명
* 예시: ShackBurger, 6.9, 토마토, 양상추, 쉑소스가 토핑된 치즈버거
* new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거")
*/
public class MenuItem {
    public String menuName;
    public double menuPrice;
    public String menuDescription;

    MenuItem (String menuName, double menuPrice, String menuDescription) {
        this.menuName = menuName;
        this.menuPrice = menuPrice;
        this.menuDescription = menuDescription;
    }

    public static void printMenuList(List<MenuItem> menuList) {
        // 반복문을 활용해 List 안에 있는 MenuItem을 하나씩 출력
        StringBuilder sb = new StringBuilder();
        sb.append("[ SHAKESHACK MENU ]\n");
        for (int i = 0; i < menuList.size(); i++) {
            sb.append(i + 1).append(". ");
            sb.append(menuList.get(i).menuName).append("\t | "); // 이름
            sb.append(menuList.get(i).menuPrice).append("\t | "); // 가격
            sb.append(menuList.get(i).menuDescription).append("\n"); // 설명
        }
        sb.append("0. 종료      | 종료");
        System.out.println(sb);
    }

    public void printMenu() {
        String menuString = "선택한 메뉴 : " + this.menuName + "\t | " + this.menuPrice + "\t | " + this.menuDescription + "\n";
        System.out.print(menuString);
    }
}
  • Lv 1에서 `List<String>`으로 관리하던 메뉴를 `MenuItem` 클래스에서 관리한다.
  • `Main` 클래스에 있던 print 메서드들을 `MenuItem` 클래스에 옮겼다. 

 

Main 클래스

package com.example.kiosk.level2;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // List 선언 및 초기화
        List<MenuItem> menuList = new ArrayList<>();
        // add 함수를 통해 new MenuItem(이름, 가격, 설명) List에 삽입
        menuList.add(new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"));
        menuList.add(new MenuItem("SmokeShack", 8.9, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"));
        menuList.add(new MenuItem("Cheeseburger", 6.9, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"));
        menuList.add(new MenuItem("Hamburger", 5.4, "비프패티를 기반으로 야채가 들어간 기본버거"));

        // Scanner 선언
        Scanner sc = new Scanner(System.in);

        while (true) {
            MenuItem.printMenuList(menuList);

            // 숫자를 입력 받기
            String selectedMenu = sc.next();
            try {
                // 반복문을 이용해서 특정 번호가 입력되면 프로그램을 종료합니다.
                if ("0".equals(selectedMenu)) break;

                // 입력받은 매뉴를 정수로 변환
                int menuNumber = Integer.parseInt(selectedMenu);

                // 메뉴 리스트에 있는 번호라면 해당 메뉴의 이름,가격,설명을 출력
                if (menuNumber > 0 && menuNumber <= menuList.size()) {
                    menuList.get(menuNumber - 1).printMenu();
                } else { // 아니라면 IllegalArgumentException 예외 발생
                    throw new IllegalArgumentException("입력 가능한 숫자는 0~" + menuList.size() + "입니다.");
                }
            } catch (NumberFormatException e) {
                System.out.println("\"" + selectedMenu + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
            }
            System.out.println("************************************************************************************");
        }
        System.out.println("프로그램을 종료합니다.");
    }
}

 


Lv 3. 순서 제어를 클래스로 관리하기

Kiosk 클래스

package com.example.kiosk.level3;

import java.util.List;
import java.util.Scanner;

/*
* Kiosk: 프로그램 순서 및 흐름 제어를 담당하는 클래스
* 설명: 키오스크 프로그램의 메뉴를 관리하고 사용자 입력을 처리하는 클래스입니다.
*/
public class Kiosk {
    // MenuItem을 관리하는 리스트가 필드로 존재합니다.
    public List<MenuItem> menuItems;

    // List<MenuItem> menuItems 는 Kiosk 클래스 생성자를 통해 값을 할당합니다.
    public Kiosk (List<MenuItem> items) {
        this.menuItems = items;
    }

    // main 함수에서 관리하던 입력과 반복문 로직은 이제 start 함수를 만들어 관리합니다.
    public void start() {
        // Scanner 선언
        Scanner sc = new Scanner(System.in);

        while (true) {
            MenuItem.printMenuList(this.menuItems);

            // 숫자를 입력 받기
            String selectedMenu = sc.next();
            try {
                // 반복문을 이용해서 특정 번호가 입력되면 프로그램을 종료합니다.
                if ("0".equals(selectedMenu)) break;

                // 입력받은 매뉴를 정수로 변환
                int menuNumber = Integer.parseInt(selectedMenu);

                // 메뉴 리스트에 있는 번호라면 해당 메뉴의 이름,가격,설명을 출력
                if (menuNumber > 0 && menuNumber <= this.menuItems.size()) {
                    this.menuItems.get(menuNumber - 1).printMenu();
                } else { // 아니라면 IllegalArgumentException 예외 발생
                    throw new IllegalArgumentException("입력 가능한 숫자는 0~" + this.menuItems.size() + "입니다.");
                }
            } catch (NumberFormatException e) {
                System.out.println("\"" + selectedMenu + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
            }
            System.out.println("************************************************************************************");
        }
        System.out.println("프로그램을 종료합니다.");
    }
}
  • MenuItem을 관리하는 리스트를 필드로 설정하고 관리한다.
  • `main`함수에서 관리하던 입력과 반복문 로직을 `start` 메서드를 호출하면 시행되도록 했다.

 

Main 클래스

package com.example.kiosk.level3;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // List 선언 및 초기화
        List<MenuItem> menuList = new ArrayList<>();
        // add 함수를 통해 new MenuItem(이름, 가격, 설명) List에 삽입
        menuList.add(new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"));
        menuList.add(new MenuItem("SmokeShack", 8.9, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"));
        menuList.add(new MenuItem("Cheeseburger", 6.9, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"));
        menuList.add(new MenuItem("Hamburger", 5.4, "비프패티를 기반으로 야채가 들어간 기본버거"));

        Kiosk kiosk = new Kiosk(menuList);
        kiosk.start();
    }
}
  • 야호 Main 클래스가 단순해졌다!

 


Lv 4. 음식 메뉴와 주문 내역을 클래스 기반으로 관리하기

Menu 클래스

package com.example.kiosk.level4;

import java.util.List;

/*
* Menu: MenuItem 클래스를 관리하는 클래스
* 예시: 버거 메뉴, 음료 메뉴 등 각 카테고리 내에 여러 MenuItem을 포함합니다.
*/
public class Menu {
    public String menuCategory;         // 메뉴 카테고리(버거, 음료, 디저트 등)
    public List<MenuItem> menuItems;    // MenuItem 클래스를 List로 관리

    public Menu(String menuCategory, List<MenuItem> menuItems) {
        this.menuCategory = menuCategory;
        this.menuItems = menuItems;
    }

    // 카테고리 메뉴를 출력하는 함수 (메인 메뉴)
    public static void printCategoryMenu(List<Menu> menuList) {
        StringBuilder sb = new StringBuilder();
        sb.append("[ MAIN MENU ]\n");
        for (int i = 0; i < menuList.size(); i++) {
            sb.append(i + 1).append(". ");
            sb.append(menuList.get(i).menuCategory).append("\n");
        }
        sb.append("0. 종료      | 종료");
        System.out.println(sb);
    }

    // Menu 객체의 menuItems 필드를 출력하는 함수
    public void printMenuItems() {
        String menuHeader = String.format("\n[ %s MENU ]\n", this.menuCategory.toUpperCase());
        StringBuilder sb = new StringBuilder(menuHeader);
        for (int i = 0; i < this.menuItems.size(); i++) {
            String menuItemString = String.format(
                    "%d. %-20s\t | W %.1f\t | %s\n",
                    i + 1,
                    this.menuItems.get(i).menuName,
                    this.menuItems.get(i).menuPrice,
                    this.menuItems.get(i).menuDescription
            );
            sb.append(menuItemString);
        }
        sb.append("0. 뒤로가기");
        System.out.println(sb);
    }

    // Menu 객체의 menuItems 중 선택된 menuItem을 출력하는 함수
    public void printSelectedMenuItem(int index) {
        String menuString = String.format(
                "선택한 메뉴 : %s\t | W %.1f\t | %s\n",
                this.menuItems.get(index).menuName,
                this.menuItems.get(index).menuPrice,
                this.menuItems.get(index).menuDescription
        );
        System.out.print(menuString);
    }
}
  • `MenuItem` 클래스를 관리하는 용도로 `Menu` 클래스 생성
    • `Kiosk` 클래스에서 필드로 관리하던 `List<MenuItem>`을 `Menu` 클래스에서 관리하도록 변경했다.
  • `MenuItem` 클래스에 있던 출력 메서드들을 `Menu` 클래스로 옮겼다.
    • 옮기면서 메서드명을 더 명확하게 변경했고, 출력 양식을 변경했다.

 

❗String.format( ) 사용

Lv 4부터 상위 카테고리와 카테고리별 메뉴를 출력하게 되었다.

 

Lv 3까지는 Burgers 메뉴만 다뤘기 때문에 출력 양식에 대해서 생각해보지 않았었다.

이전 출력 양식은 메뉴의 이름, 가격, 설명을 \t로 구분하여 출력하였다.

해당 양식으로 출력해도 간격이 일정했던 이유는 버거 매뉴들의 이름(menuName)이 길이가 비슷해서 \t으로 띄었을 때 띄어지는 범위가 같았던 것이다..!

 

버거 말고도 다른 카테고리별 메뉴들을 추가하고 출력해보니 너무!! 보기 싫은 모양이 되었다.

으악

 

메뉴 이름 길이가 차이나도 일정하게 출력하기 위해서 출력 양식을 `String.format`을 이용해서 정해줬다.

변경 전 코드

StringBuilder sb = new StringBuilder();
sb.append("\n[ ").append(this.menuCategory.toUpperCase()).append(" MENU ]\n");
for (int i = 0; i < this.menuItems.size(); i++) {
	sb.append(i + 1).append(". ");
	sb.append(this.menuItems.get(i).menuName).append("\t | ");      		// 이름
	sb.append("W ").append(this.menuItems.get(i).menuPrice).append("\t | ");	// 가격
	sb.append(this.menuItems.get(i).menuDescription).append("\n");  		// 설명
}
sb.append("0. 뒤로가기");
System.out.println(sb);

변경 후 코드

String menuHeader = String.format("\n[ %s MENU ]\n", this.menuCategory.toUpperCase());
StringBuilder sb = new StringBuilder(menuHeader);
for (int i = 0; i < this.menuItems.size(); i++) {
	String menuItemString = String.format(
		"%d. %-20s\t | W %.1f\t | %s\n",
		i + 1,
		this.menuItems.get(i).menuName,
		this.menuItems.get(i).menuPrice,
		this.menuItems.get(i).menuDescription
	);
	sb.append(menuItemString);
}
sb.append("0. 뒤로가기");
System.out.println(sb);
  • `%-20s`로 `menuName`을 출력하고 남은 길이는 오른쪽부터 공백으로 채워지도록 했다.

변경 후 일정한 간격으로 메뉴가 출력되는 것을 확인할 수 있다!


Kiosk 클래스

package com.example.kiosk.level4;

import java.util.List;
import java.util.Scanner;

/*
 * Kiosk: 프로그램 순서 및 흐름 제어를 담당하는 클래스
 * 설명: 키오스크 프로그램의 메뉴를 관리하고 사용자 입력을 처리하는 클래스입니다.
 */
public class Kiosk {
    // Menu를 관리하는 리스트 필드
    public List<Menu> menuList;
    
    // 생성자를 통해 List<Menu> menuList 필드 값 할당
    public Kiosk(List<Menu> menuList) {
        this.menuList = menuList;
    }

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

        while (true) {
            // 카테고리 메뉴 출력
            Menu.printCategoryMenu(this.menuList);

            String selectedMenu = sc.next();
            try {
                if ("0".equals(selectedMenu)) break;

                int menuNumber = Integer.parseInt(selectedMenu);
                if (menuNumber > 0 && menuNumber <= this.menuList.size()) {
                    // 카테고리 메뉴 중 선택된 메뉴(menuItems) 출력
                    startViewSelectedCategoryMenu(menuNumber - 1);
                } else {
                    throw new IllegalArgumentException("입력 가능한 숫자는 0~" + this.menuList.size() + "입니다.");
                }
            } catch (NumberFormatException e) {
                System.out.println("\"" + selectedMenu + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
            }
            System.out.println();
        }
        System.out.println("프로그램을 종료합니다.");
    }

    private void startViewSelectedCategoryMenu(int index) {
        // 선택된 menuItems 필드 출력
        this.menuList.get(index).printMenuItems();

        Scanner sc = new Scanner(System.in);
        String selectedMenuItem = sc.next();
        try {
            if ("0".equals(selectedMenuItem)) return;
            int menuItemNumber = Integer.parseInt(selectedMenuItem);
            if (menuItemNumber > 0 && menuItemNumber <= this.menuList.get(index).menuItems.size()) {
                // menuItems 중 선택된 menuItem 출력
                this.menuList.get(index).printSelectedMenuItem(menuItemNumber - 1);
            } else {
                throw new IllegalArgumentException("입력 가능한 숫자는 0~" + this.menuList.get(index).menuItems.size() + "입니다.");
            }
        } catch (NumberFormatException e) {
            System.out.println("\"" + selectedMenuItem + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        } catch (Exception e) {
            System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
        }
    }
}
  • Lv 4부터 카테고리 메뉴(Main Menu)를 출력하고 선택한 카테고리에 대해 메뉴 아이템들을 출력하도록 설계되었다.
    • 카테고리 메뉴(버거, 음료 등)를 출력하고 카테고리를 선택하는 것은 `start` 메서드에서 관리하고,
    • 메뉴 아이템들(햄버거, 치즈버거 등)을 출력하고 메뉴를 선택하는 것은 `startViewSelectedCategoryMenu` 메서드에서 관리하도록 했다.
  • `Menu` 클래스를 리스트로 관리하는 필드를 생성하여 상위 메뉴들을 관리해준다.

 

❗요구 사항에 대한 고민

Lv 4 요구 사항

Lv 4의 요구 사항 중 위 부분이 헷갈려서 설계하는데 시간을 썼다.

구현을 마치고 다시 읽어보니 좀 당연한거 같기도 하다,, 😅 하하! (튜터님께 질문 드리고 확신을 얻음!)

 

🤔 상위 카테고리 메뉴 관리 방법에 대한 고민

'List<Menu>'를 `start` 메서드의 매개변수로 받아야할지, `Kiosk`의 필드로 를 관리해야하는지 고민이 됐다.

➡️ 고민의 이유: `Kiosk` 클래스는 전체 순서 제어 해야하는 클래스인가?

➡️ 결론: `Kiosk` 클래스에서 `List<MenuItem>`을 관리하는 건 부적절하지만, `List<Menu>`를 필드로 갖는 것은 자연스러운 설계라고 판단했다.

 

🤓 `Kiosk` 클래스에 List<Menu> 필드를 생성한 이유

1. 상위 카테고리 메뉴 관리

`List<Menu>`는 현재 프로그램에서 수정되지 않는 데이터이다.

필드로 정의해서 사용하면 전역적으로 관리할 수 있다고 생각했다.

 

2. 메뉴 세부항목은 `Menu`가, 전체적인 순서나 흐름은 `Kiosk`가 관리

`List<Menu>`는 `start`메서드에서 매번 사용해야하는 데이터이므로, 전체 순서를 제어하는 `Kiosk` 클래스에서 필드로 관리하는 것이 각 클래스 역할에 맞다고 생각했다.

 

3. 가독성과 유지보수성

`Kiosk` 클래스에서 메뉴 데이터를 사용할 때 매번 매개변수로 전달하는 대신, 필드로 관리하면 가독성과 유지보수성에 좋을 것이라고 생각했다.


Main 클래스

package com.example.kiosk.level4;

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Burgers 메뉴
        List<MenuItem> burgersMenuItems = Arrays.asList(
                new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"),
                new MenuItem("SmokeShack", 8.9, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"),
                new MenuItem("Cheeseburger", 6.9, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"),
                new MenuItem("Hamburger", 5.4, "비프패티를 기반으로 야채가 들어간 기본버거")
        );
        // Drinks 메뉴
        List<MenuItem> drinksMenuItems = Arrays.asList(
                new MenuItem("Shack-made Lemonade", 3.9, "매장에서 직접 만드는 상큼한 레몬에이드"),
                new MenuItem("Fifty/Fifty", 3.5, "레몬에이드와 아이스티의 만남"),
                new MenuItem("Fountain Soda", 2.7, "코카콜라, 코카콜라 제로, 스프라이트, 환타 오렌지, 환타 그레이프")
        );
        // Desserts 메뉴
        List<MenuItem> dessertsMenuItems = Arrays.asList(
                new MenuItem("Red Bean Shake", 6.5, "신선한 커스터드와 함께 우유와 레드빈이 블랜딩 된 시즈널 쉐이크"),
                new MenuItem("Shack Attack", 8.9, "초콜렛 처지 소스, 초콜렛 트리플 쿠키, Lumiere 초콜렛 청크와 스프링클이 들어간 진한 초콜렛 커스터드"),
                new MenuItem("Honey Butter Crunch", 8.9, "달콤한 허니버터소스와 슈가콘 쿠키가 함께하는 바닐라 커스터드")
        );

        // Menu 객체 생성
        Menu burgersMenu = new Menu("Burgers", burgersMenuItems);
        Menu drinksMenu = new Menu("Drinks", drinksMenuItems);
        Menu dessertsMenu = new Menu("Desserts", dessertsMenuItems);
        // Menu 객체 리스트 생성
        List<Menu> menuList = Arrays.asList(burgersMenu, drinksMenu, dessertsMenu);

        // Kiosk 객체 생성
        Kiosk kiosk = new Kiosk(menuList);
        // Kiosk 내 시작하는 함수 호출
        kiosk.start();
    }
}
  • `Menu` 객체에 넣을 `List<MenuItem>`을 main 함수에서 선언하고 전달하도록 했다.

 


Lv 5. 캡슐화 적용하기

각 클래스의 필드에 캡슐화를 적용하여 데이터를 보호했다.

 

1. 필드의 접근 제어자 변경 

각 클래스의 필드들의 접근제어자를 public에서 private로 변경

 

2. Getter 메서드 추가
필드 값은 읽기만 가능하도록 Getter 메서드를 생성

현재 프로그램에서 필드 값들은 모두 생성자로 값이 초기화되며, 외부에서 수정이 불필요하므로 Setter 메서드는 생성하지 않았다.

 

MenuItem 클래스

package com.example.kiosk.level5;

/*
 * MenuItem: 세부 메뉴 속성 가지는 클래스
 * 햄버거의 이름, 가격설명
 * 예시: ShackBurger, 6.9, 토마토, 양상추, 쉑소스가 토핑된 치즈버거
 * new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거")
 */
public class MenuItem {
    private String menuName;
    private double menuPrice;
    private String menuDescription;

    public String getMenuName() {
        return menuName;
    }

    public double getMenuPrice() {
        return menuPrice;
    }

    public String getMenuDescription() {
        return menuDescription;
    }

    public MenuItem (String menuName, double menuPrice, String menuDescription) {
        this.menuName = menuName;
        this.menuPrice = menuPrice;
        this.menuDescription = menuDescription;
    }
}

 

Menu 클래스

package com.example.kiosk.level5;

import java.util.List;

/*
 * Menu: MenuItem 클래스를 관리하는 클래스
 * 예시 : 버거 메뉴, 음료 메뉴 등 각 카테고리 내에 여러 MenuItem을 포함합니다.
 */
public class Menu {
    private final String menuCategory;         // 메뉴 카테고리(버거, 음료, 디저트 등)
    private final List<MenuItem> menuItems;    // MenuItem 클래스를 List로 관리

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

    public String getMenuCategory() {
        return menuCategory;
    }

    public Menu(String menuCategory, List<MenuItem> menuItems) {
        this.menuCategory = menuCategory;
        this.menuItems = menuItems;
    }

    // 카테고리 메뉴를 출력하는 함수 (메인 메뉴)
    public static void printCategoryMenu(List<Menu> menuList) {
        StringBuilder sb = new StringBuilder();
        sb.append("[ MAIN MENU ]\n");
        for (int i = 0; i < menuList.size(); i++) {
            sb.append(i + 1).append(". ");
            sb.append(menuList.get(i).menuCategory).append("\n");
        }
        sb.append("0. 종료      | 종료");
        System.out.println(sb);
    }

    // Menu 객체의 menuItems 필드를 출력하는 함수
    public void printMenuItems() {
        String menuHeader = String.format("\n[ %s MENU ]\n", this.menuCategory.toUpperCase());
        StringBuilder sb = new StringBuilder(menuHeader);
        for (int i = 0; i < this.menuItems.size(); i++) {
            String menuItemString = String.format(
                    "%d. %-20s\t | W %.1f\t | %s\n",
                    i + 1,
                    this.menuItems.get(i).getMenuName(),
                    this.menuItems.get(i).getMenuPrice(),
                    this.menuItems.get(i).getMenuDescription()
            );
            sb.append(menuItemString);
        }
        sb.append("0. 뒤로가기");
        System.out.println(sb);
    }

    // Menu 객체의 menuItems 중 선택된 menuItem을 출력하는 함수
    public void printSelectedMenuItem(int index) {
        String menuString = String.format(
                "선택한 메뉴 : %s\t | W %.1f\t | %s\n",
                this.menuItems.get(index).getMenuName(),
                this.menuItems.get(index).getMenuPrice(),
                this.menuItems.get(index).getMenuDescription()
        );
        System.out.print(menuString);
    }
}

 

Kiosk 클래스

package com.example.kiosk.level5;

import java.util.List;
import java.util.Scanner;

/*
 * Kiosk: 프로그램 순서 및 흐름 제어를 담당하는 클래스
 * 설명: 키오스크 프로그램의 메뉴를 관리하고 사용자 입력을 처리하는 클래스입니다.
 */
public class Kiosk {
    // Menu를 관리하는 리스트 필드
    private final List<Menu> menuList;

    // 생성자를 통해 List<Menu> menuList 필드 값 할당
    public Kiosk(List<Menu> menuList) {
        this.menuList = menuList;
    }

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

        while (true) {
            // 카테고리 메뉴 출력
            Menu.printCategoryMenu(this.menuList);

            String selectedMenu = sc.next();
            try {
                if ("0".equals(selectedMenu)) break;

                int selectedMenuNumber = Integer.parseInt(selectedMenu);
                int categoryCount = this.menuList.size();
                if (selectedMenuNumber > 0 && selectedMenuNumber <= categoryCount) {
                    // 카테고리 메뉴 중 선택된 메뉴(menuItems) 출력
                    startViewSelectedCategoryMenu(selectedMenuNumber - 1);
                } else {
                    throw new IllegalArgumentException("입력 가능한 숫자는 0~" + categoryCount + "입니다.");
                }
            } catch (NumberFormatException e) {
                System.out.println("\"" + selectedMenu + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
            }
            System.out.println();
        }
        System.out.println("프로그램을 종료합니다.");
    }

    private void startViewSelectedCategoryMenu(int index) {
        // 선택된 menuItems 필드 출력
        this.menuList.get(index).printMenuItems();

        Scanner sc = new Scanner(System.in);
        String selectedMenuItem = sc.next();
        try {
            if ("0".equals(selectedMenuItem)) return;
            int selectedMenuItemNumber = Integer.parseInt(selectedMenuItem);
            int menuItemsCount = this.menuList.get(index).getMenuItems().size();
            if (selectedMenuItemNumber > 0 && selectedMenuItemNumber <= menuItemsCount) {
                // menuItems 중 선택된 menuItem 출력
                this.menuList.get(index).printSelectedMenuItem(selectedMenuItemNumber - 1);
            } else {
                throw new IllegalArgumentException("입력 가능한 숫자는 0~" + menuItemsCount + "입니다.");
            }
        } catch (NumberFormatException e) {
            System.out.println("\"" + selectedMenuItem + "\"은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        } catch (Exception e) {
            System.out.println("알 수 없는 오류가 발생했습니다. : " + e.getMessage());
        }
    }
}

 

Main 클래스

package com.example.kiosk.level5;

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Burgers 메뉴
        List<MenuItem> burgersMenuItems = Arrays.asList(
                new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"),
                new MenuItem("SmokeShack", 8.9, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"),
                new MenuItem("Cheeseburger", 6.9, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"),
                new MenuItem("Hamburger", 5.4, "비프패티를 기반으로 야채가 들어간 기본버거")
        );
        // Drinks 메뉴
        List<MenuItem> drinksMenuItems = Arrays.asList(
                new MenuItem("Shack-made Lemonade", 3.9, "매장에서 직접 만드는 상큼한 레몬에이드"),
                new MenuItem("Fifty/Fifty", 3.5, "레몬에이드와 아이스티의 만남"),
                new MenuItem("Fountain Soda", 2.7, "코카콜라, 코카콜라 제로, 스프라이트, 환타 오렌지, 환타 그레이프")
        );
        // Desserts 메뉴
        List<MenuItem> dessertsMenuItems = Arrays.asList(
                new MenuItem("Red Bean Shake", 6.5, "신선한 커스터드와 함께 우유와 레드빈이 블랜딩 된 시즈널 쉐이크"),
                new MenuItem("Shack Attack", 8.9, "초콜렛 처지 소스, 초콜렛 트리플 쿠키, Lumiere 초콜렛 청크와 스프링클이 들어간 진한 초콜렛 커스터드"),
                new MenuItem("Honey Butter Crunch", 8.9, "달콤한 허니버터소스와 슈가콘 쿠키가 함께하는 바닐라 커스터드")
        );

        // Menu 객체 생성
        Menu burgersMenu = new Menu("Burgers", burgersMenuItems);
        Menu drinksMenu = new Menu("Drinks", drinksMenuItems);
        Menu dessertsMenu = new Menu("Desserts", dessertsMenuItems);
        // Menu 객체 리스트 생성
        List<Menu> menuList = Arrays.asList(burgersMenu, drinksMenu, dessertsMenu);

        // Kiosk 객체 생성
        Kiosk kiosk = new Kiosk(menuList);
        // Kiosk 내 시작하는 함수 호출
        kiosk.start();
    }
}

 


마무리

이번 과제에서는 Git 브랜치와 커밋 메시지 작성에 신경을 많이 썼습니다.

잘한 건지는 나중에 피드백을 받아봐야 알겠지만,, 저번 과제보다 Git을 더 적극적으로 활용하면서 공부가 많이 됐습니다 🎵

https://github.com/mannaKim/java-kiosk-project

 

GitHub - mannaKim/java-kiosk-project

Contribute to mannaKim/java-kiosk-project development by creating an account on GitHub.

github.com