Sparta/Projects

[Project] 키오스크를 만들어보자

syuare 2025. 5. 1. 13:40

 

 

[요구사항]

 

LV1. 기본적인 키오스크를 만들자

더보기

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

 `Scanner`를 사용하여 여러 햄버거 메뉴를 출력합니다.
제시된 메뉴 중 입력받은 숫자에 따라 다른 로직을 실행하는 코드를 작성합니다.
반복문을 이용해서 특정 번호가 입력되면 프로그램을 종료합니다.

LV2. 객체 지향 설계 적용 > 메뉴를 클래스로 관리해보자

더보기

메뉴를 List를 통해 관리해보자

`MenuItem` 클래스 생성하기 (`이름`, `가격`, `설명` 필드)

`main` 함수에서 햄버거 메뉴를 출력하기.

반복문을 활용해 `menuItems`를 탐색하기

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

더보기

Kiosk 클래스 생성하기

`MenuItem`을 리스트(List)로 관리하기

`start` 함수에서 입력과 반복문 로직 관리하기

 

요구사항

  • 키오스크 프로그램을 시작하는 메서드가 구현하기
  • 콘솔에 메뉴를 출력하기
  • 사용자의 입력을 받아 메뉴를 선택하거나 프로그램을 종료하기
  • 유효하지 않은 입력에 대해 오류 메시지를 출력하기
  • `0`을 입력하면 프로그램이 ‘뒤로가기’되거나 ‘종료’되도록 하기

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

더보기

Menu 클래스 생성하기

각 카테고리 내에 여러 `MenuItem`을 포함하기

`List`는 Menu 클래스가 관리하도록 변경하기

각 메뉴를 포함하는 상위 개념의 카테고리 이름` 필드 구현하기

메뉴 카테고리 이름을 반환하는 메서드 구현하기

LV5. 캡슐화 적용하기

더보기

 클래스의 필드에 직접 접근하지 못하도록 설정하기

 Getter와 Setter 메서드를 사용해 데이터를 관리하기


도전 LV1. 장바구니 / 구매 기능 추가하기

더보기

장바구니 생성 / 관리 

  • 사용자가 선택한 메뉴를 장바구니에 추가하는 기능 구현 (메뉴명, 수량, 가격 정보 저장 + 동적으로 추가&조회 가능)
  • 유효하지 않는 입력에 대한 예외 처리 필요

장바구니 출력 / 금액 계산

  • 사용자가 결제 시도 전 장바구니에 담긴 모든 메뉴와 총 금액 출력

장바구니 담기 기능

  • 메뉴 클릭 시 장바구니 추가 여부 확인
  • 입력에 따라 추가/취소 처리
  • 장바구니 담은 목록 출력

주문 기능

  • 장바구니 담긴 모든 항목 출력
  • 합산하여 총금액 계산
  • 주문하기 입력 시 장바구니 초기화

 

요구 사항

  • ✅ 장바구니에 메뉴가 추가되지 않았을 경우 기본 메뉴에서 출력되지 않도록 하기
    • 장바구니에 메뉴가 추가되면 장바구니 확인 메뉴 출력되도록 하기

도전 LV2. 주문 / 장바구니 관리

더보기

사용자 유형별 할인율 관리하기 (Enum)

  • 주문 시 유형에 맞는 할인율 적용하여 총 금액 계산

장바구니 조회 기능 구현 (Lambda&Stream)

  • 기존 장바구니에서 특정 메뉴 빼기 기능 구현 (stream.filter)

더보기

도전 LV 포함 시


 

[트러블 슈팅]

1. 입력/반복문 로직을 main 메서드 → Kisok 클래스 내 stratKiosk 메서드로 구현하였으나 입력값에 따른 로직이 정상적으로 실행되지 않는 문제

더보기

상세 내용

Kiosk 클래스에서 startKiosk 메서드를 생성, Scanner를 활용해서 입력값을 받고, 특정 키(0)을 입력하지 않을 경우 반복되도록 코드를 구현하였다.

 

해당 메서드를 Main 클래스의 main 메서드에서 호출하여 실행을 한 결과

  • 실제 입력값(inputStr,inputKey) 을 숫자 1로 입력하였고,
  • 기대하는 결과는 menu.menuBoardSingle(inputKey) 메서드를 호출하여 단일 메뉴의 설명을 출력하도록 하였는데, 
  • 실제 출력된 결과는 "잘못 입력하였습니다" 와 같은 메시지가 출력되었다. 
// 입력, 기능 반복문
public void startKiosk (){
	
    public static Scanner scanner = new Scanner(System.in);
    private final Menu menu = new Menu();

	while(true){
		System.out.print("메뉴를 선택해주세요(0: 종료): ");
		String inputStr = scanner.nextLine().trim(); //입력
		try {
			int inputKey = Integer.parseInt(inputStr); // 입력값 > 정수로
			if(inputKey == 0) { // 탈출
				System.out.println("키오스크를 종료합니다.");
				break;
			} else if (inputKey >= 1 && inputKey <= menu.getMenuItems().size()) {
				// 버거 메뉴판 - 단일 상품 출력
				menu.menuBoardSingle(inputKey);
			} else {
				System.out.println("잘못 입력하셨습니다: " + inputKey);
			}
		} catch (NumberFormatException e) {
			System.out.println("숫자를 입력해주세요: " + inputStr);
		}
	}
}

public static void main(String[] args) {
    Menu menu = new Menu();
    Kiosk kiosk = new Kiosk();.

    // ... 데이터 입력 

    menu.menuBoard();
    kiosk.startKiosk();
}

해결 시도 방법

  • 1. 실제 입력값이 inputKey 값으로 반환되는지 확인
  • 2. menu.getMenuItems().size() 의 값이 제대로 들고오는지 확인
  • 3. else if 문의 조건값이 잘 동작하는지 확인

 

1. print 메서드를 통해 숫자 1을 입력했을 때 inputKey에 저장된 값을 출력해보니 "1"로 정상적으로 확인되었다.

2. main 메서드에서 menu.getMenuItems().size() 값을 확인해본 결과 현재 4개의 요소가 포함되어 있어 size 값도 4로 정상 출력되었다.

3. 숫자 1 입력 시 startKiosk 메서드 내에서 (inputKey>=1) 과 (inputKey <= menu.getMenuItems().size()) 이 제대로 동작하는지 확인해보았다.

  • inputKey >= 1 : true로 정상 동작하는 것을 확인
  • inputKey <= menu.getMenuItems().size(): false 로 정상 동작하지 않는 것으로 확인

main 메서드에서는 size 값이 잘 반환이 되었는데, startKiosk 메서드에서는 size값이 정상적으로 반환되지 않았음을 확인

 

원인

Menu 클래스 내 컬렉션(List) menuItems의 데이터 값을 main 메서드에서 추가(add) 하였는데

Main 클래스에서 Menu 객체를 신규로 인스턴스화 하였고, 

Kiosk 클래스에서도 Menu 객체를 신규로 인스턴스화 한 것이 원인이었다.

public class Kiosk {
	private final Menu menu = new Menu();
    
    // ....
}

public static void main(String[] args) {
	Menu menu = new Menu();
	Kiosk kiosk = new Kiosk();
    // .....
}

 

데이터 값은 main 메서드에 생성한 menu라는 객체에 추가했으니 main 메서드의 menu 객체에 데이터가 저장되어있다.

 

그런데 Kiosk 클래스에서 새로 생성한 menu라는 객체는 아무런 데이터가 없는데 Kiosk 클래스 내 startKiosk 메서드에서는 menu 객체의 데이터를 불러온 것이다. (동명이인 같은 느낌, 이름은 같은데 다른 객체)

 

해결 방법

 

데이터값을 추가한 main 메서드에 생성한 menu 객체와 Kiosk 클래스에 생성된 menu 객체의 데이터 값을 일치시키는 작업을 하고, 동일한 값을 가질 수 있도록 Kiosk 클래스 내 생성자를 추가하여 코드를 작성하였더니 문제가 해결되었다.

public class Kiosk {
    private final Menu menu;

    // 생성자 // menu 객체의 데이터를 가진 Kiosk 객체를 선언할 수 있도록 생성자 추가
    public Kiosk(Menu menu){
        this.menu = menu;
    }
    
    // ...
}


public static void main(String[] args) {
	Menu menu = new Menu();

// ... 데이터 입력

	menu.menuBoard();
	Kiosk kiosk = new Kiosk(menu); // 데이터를 추가한 후 객체 선언(변수 menu를 가진..)
	kiosk.startKiosk();
}

2. LV4 카테고리 별로 데이터를 분리하기 위해 3개의 List를 활용하였는데, 출력 등 활용을 위해서 동일한 코드이지만 카테고리별로 분리된 메서드 (getter, menu 출력 등)을 3개씩 작성해야 하는데, 이 코드를 간결화 하고 싶다. (진행 X)

더보기

HashMap을 이용하면 하나의 Map으로 관리할 수 있다고 해서 변경해보자

 

(기존) 3개의 List로 코드 작성 

public class Menu {

    // 속성
    // 카테고리별 메뉴 List
    private final List<MenuItem> burgerMenu = new ArrayList<>();
    private final List<MenuItem> drinkMenu = new ArrayList<>();
    private final List<MenuItem> dessertMenu = new ArrayList<>();


    // 기능(메서드)
    // 메뉴 추가 - category
    public void addMenu(MenuItem menuItem) {
        switch(menuItem.getCategory()) {
            case "Burger":
                burgerMenu.add(menuItem);
                break;
            case "Drink":
                drinkMenu.add(menuItem);
                break;
            case "Dessert":
                dessertMenu.add(menuItem);
                break;
        }
    }

    // 기본 메뉴
    public void menuSelect() {
        System.out.println("[ 기본 메뉴 ]");
        System.out.println("1. Burger");
        System.out.println("2. Drink");
        System.out.println("3. Dessert");
        System.out.println("0. Kiosk End");
    }

    // 버거 메뉴
    public void menuBoardBurger() {
        int num =1;
        System.out.println("[ 맥도나루도 - 버거 ]");
        for(MenuItem item : burgerMenu) {
            System.out.println(num + ". " + item);
            num++;
        }
    }

    // 음료 메뉴
    public void menuBoardDrink() {
        int num =1;
        System.out.println("[ 맥도나루도 - 음료 ]");
        for(MenuItem item : drinkMenu) {
            System.out.println(num + ". " + item);
            num++;
        }
    }

    // 디저트 메뉴
    public void menuBoardDessert() {
        int num =1;
        System.out.println("[ 맥도나루도 - 디저트 ]");
        for(MenuItem item : dessertMenu) {
            System.out.println(num + ". " + item);
            num++;
        }
    }

    // 버거 단품 메뉴 조회
    public void singleMenuBurger(int index) {
        MenuItem select = burgerMenu.get(index-1);
        System.out.println(select);
    }

    // 음료 단품 메뉴 조회
    public void singleMenuDrink(int index) {
        MenuItem select = drinkMenu.get(index-1);
        System.out.println(select);
    }

    // 디저트 단품 메뉴 조회
    public void singleMenuDessert(int index) {
        MenuItem select = dessertMenu.get(index-1);
        System.out.println(select);
    }

    public List<MenuItem> getBurgerMenu() {
        return burgerMenu;
    }

    public List<MenuItem> getDrinkMenu() {
        return drinkMenu;
    }

    public List<MenuItem> getDessertMenu() {
        return dessertMenu;
    }

}

 

(수정) HashMap 사용

1

 

3. 장바구니 메뉴 결제 이후 기본 메뉴로 이동되지 않고, 카테고리 메뉴로 이동되는 문제

더보기

문제 상황

카테고리 별 메뉴(버거, 음료, 디저트)에서

특정 메뉴를 선택 > 장바구니 추가 후 결제를 시도할 경우

기본 메뉴가 아닌 카테고리별 메뉴를 이동하는 문제가 발생하였다.

  • 기본 메뉴(menuSelectMain 메서드)에서
  • 4. 장바구니 조회하여 결제를 할 경우에는
  • 기본 메뉴를 바로 이동되어 문제가 발생하지 않았다.

결제 완료되면 기본 메뉴로 이동되는 것이 자연스럽다고 생각되는데,

실제 구현 결과로는 결제 완료 후 카테고리 별 메뉴로 이동되는 것이 부자연스럽다고 생각했다.

 

기대 결과

장바구니 조회 후 결제 시 기본 메뉴로 이동되는 것과 동일하게

카테고리별 메뉴에서 메뉴 선택 > 장바구니 추가 > 바로 결제 > 기본 메뉴로 이동되도록 구현하고자 했다.

 

해결 시도

1. 장바구니 결제 메서드에서 결제 완료 후 강제로 menuSelectMain() 메서드를 호출하여 기본 메뉴를 출력하도록 구현해보았으나, 문제가 해결되지 않았다. (오히려 강제로 기본 메뉴를 출력한 상황에서 0.종료를 입력하니 인식을 하지 못하였다.)

 

2. return; 이 아닌 break; continue 등을 사용해보았지만, 문제 해결이 되지 않았다.

 

문제 원인 (김동현 튜터님께서 도움주셨습니다.)

구현된 반복문 마다 출력문(print) 코드를 추가하여 디버깅을 진행한 결과,

카테고리별 메뉴에서 메뉴 선택 기능이 작동할 경우 해당 반복문을 빠져나오지 못하는 것을 확인하였다.

 

그 이유는 아래와 같이 메뉴 선택하는 조건에서 break; 코드가 없어서 반복문을 빠져나오지 못했던 것이다.

int dstNum = Integer.parseInt(dessertSelect); // 입력값 > 정수로
if(dstNum == 0) { // 탈출
	System.out.println("===============================");
	System.out.println("뒤로 돌아갑니다.");
	break;
} else if (dstNum >= 1 && dstNum <= menu.getDessertMenu().size()) {
	// 버거 메뉴판 - 단일 상품 출력
	menu.singleMenuDessert(dstNum);
	DessertAddToCart(dstNum); // 장바구니(디저트) 추가
    // break; << 가 없어서 반복문을 빠져나오지 못하고 있다.
}

 

 

문제 해결

각 카테고리 별 메뉴 메서드에 break; 코드를 추가하였더니 원하는 결과값이 출력되는 것을 확인하였다.

 

정리/회고

점점 심화된 기술의 코드를 사용해야되는데, 개념을 이해하는데는 시간이 필요하고 프로젝트를 진행하려고 하면 시간이 없다. 

먼저 개발과 공부를 병행해야하는 상황에서 시간 배분이 매우 중요함을 깨닫게 되었다.

 

또한 개발에 임하는 생각?태도?자세? 에 대해서 고민을 해보게 되었다.

개발을 할 때 조금 더 좋은 코드를 작성하려는 욕심이 강한 것 같다.

 

문제는 프로그램 전체를 한 차례 완성한 후 좋은 코드를 작성하기 위해 수정을 여러 차례 반복하는 것은 문제가 없을 것 같지만,

현재의 나는 프로그램이 완성도 되지 않았는데, 머릿 속에 떠오르는 조금 더 좋다고 생각되는 코드를 작성하기 위해 여러 차례 수정을 하는 습관이 있는 것 같다.

 

이로 인해서 코드가 점점 좋은 코드로 수정되는 것은 좋을 수 있다.

하지만 개발자로서 마감일이 존재하는 제품(프로그램)을 완성하지 못하는 것은 문제가 있다고 생각한다.

 

그래서 결과는, 이번에도 모든 도전 과제 중 LV2를 전부 구현하지 못했는데, 

도전 과제 LV1은 구현했으나 시간이 부족하보니  LV2 중에서는 선택과 집중을 하게 되었다.
(계산기에서는 Enum을 했으니, 이번에는 Lambda&Stream을 구현)

 

그리고 마감 당일에 도전 과제 1~2LV 를 진행하다보니 보다 더 디테일함을 챙기지 못했다. (예외 처리 등)

당장의 마감 시간이 급급해서 코드를 구현하는 것에 그칠 수 밖에 없었던 것 같고, 

클래스 분리, 객체 지향의 원칙 SOILD 부분도 아쉽게 느껴지고,

계속 생각해보면 해볼 수록 계속 아쉬운 것 같다. (물론 과제 끝나게 도전과제 LV 2 모두 구현 완료)

 

이번 프로젝트로 코드 작성의 어려움도 있겠지만, 개발에 임하는 자세(생각,태도)에 대해 진지하게 고민해보았던 프로젝트였다.

좋은 코드가 중요한가? 마감일이 중요한가.? → 마감일에 맞춰 프로그램을 완성하에 제출(출품)하는게 더 중요하다고 결론을...

 

결과물 자체에 대해서는 계산기 프로젝트를 했을 때보다 더욱 지식을 흡수했다고 생각했는데,

차라리 결과물은 계산기 LV3 때가 조금 더 낫지 않았나 라는 생각이 들어 많이 아쉬움이 있었다.

 

아무튼 장/단점이 명확한 이 자세,태도를 어떻게 개선하고 발전해나갈 지 고민을 계속하게 되고,

자신을 되돌아보게 되는 프로젝트였던 것 같다.

 

*좋은 코드: 가독성 좋은 코드, 유지보수가 편한 코드 → 객체 지향 설계를 만족하는 코드?