25.1.13(월)
Lv1 트러블슈팅
문제 : switch문 안에 case 0:을 넣을 경우, 0을 입력했을 때 프로그램이 종료되지 않는다.
원인 : break를 해도, 가장 가까운 switch문에서 나오기 때문에 while문은 계속 반복 된다.
1차 해결 방법
if문으로 따로 빼서 사용한다.
if(inputNumber==0){
System.out.println("프로그램을 종료합니다.");
break;
}
문제 : default에 1,2,3,4가 아닌 경우는 모두 exception을 반환한다.
원인 : if문을 실행하기도 전에 exception이 발생한다.
2차 해결방법
if문과 switch문 위치를 수정한다.
if(inputNumber==0){
System.out.println("프로그램을 종료합니다.");
break;
}
switch (inputNumber){
case 1:
System.out.println("1. ShackBurger | W 6.9 | 토마토, 양상추, 쉑소스가 토핑된 치즈버거");
break;
case 2:
System.out.println("2. SmokeShack | W 8.9 | 베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거");
break;
case 3:
System.out.println("3. Cheeseburger | W 6.9 | 포테이토 번과 비프패티, 치즈가 토핑된 치즈버거");
break;
case 4:
System.out.println("4. Hamburger | W 5.4 | 비프패티를 기반으로 야채가 들어간 기본버거");
break;
default:
throw new IllegalStateException("보기 중에 없는 번호이거나 숫자가 아닙니다. 다시 입력해주세요.");
}
요구사항 : switch문 안에 모든 조건들을 넣고 싶다.
원인 : while문이 종료되어야 한다.
3차 해결방법
while문 안에 boolean타입 변수에 true를 초기화하여 넣고 0에 해당하면 false로 값을 수정한다.
boolean isOkay = true;
while (isOkay){ /* 중략 */ }
switch (inputNumber){
case 0:
System.out.println("프로그램을 종료합니다.");
isOkay = false;
break;
/* 중략 */
}
4차 해결방법(팀원분 피드백 반영)
return문을 사용해서 프로그램이 종료되도록 한다.
switch (inputNumber){
case 0:
System.out.println("프로그램을 종료합니다.");
return;
/* 중략 */
}
Lv2 트러블슈팅
add 함수를 통해 new MenuItem(이름, 가격, 설명) List에 삽입
MenuItem shackBurger = new MenuItem("ShackBurger",6.9,"토마토, 양상추, 쉑소스가 토핑된 치즈버거");
menuItems.add(shackBurger);
MenuItem SmokeShack = new MenuItem("SmokeShack",8.9,"베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거");
menuItems.add(SmokeShack);
MenuItem Cheeseburger = new MenuItem("Cheeseburger",6.9,"포테이토 번과 비프패티, 치즈가 토핑된 치즈버거");
menuItems.add(Cheeseburger);
MenuItem Hamburger = new MenuItem("Hamburger",5.4,"비프패티를 기반으로 야채가 들어간 기본버거");
menuItems.add(Hamburger);
튜터님 피드백 : List.of를 활용
menuItems = List.of(shackBurger,SmokeShack,Cheeseburger,Hamburger);
25.1.14(화)
Lv3 트러블슈팅
튜터님 피드백 final 활용
private final List<MenuItem> menuItems;
그런데 참조 대상의 객체 값은 변경할 수 있다.
참조형 변수 `data` 에 `final` 이 붙었다. 이 경우 참조형 변수에 들어있는 참조값을 다른 값으로 변경하지 못한
다. 쉽게 이야기해서 이제 다른 객체를 참조할 수 없다. 그런데 이것의 정확한 뜻을 잘 이해해야 한다. 참조형 변수
에 들어있는 참조값만 변경하지 못한다는 뜻이다. 이 변수 이외에 다른 곳에 영향을 주는 것이 아니다.
`Data.value` 는 `final` 이 아니다. 따라서 값을 변경할 수 있다
정리하면 참조형 변수에 `final` 이 붙으면 참조 대상을 자체를 다른 대상으로 변경하지 못하는 것이지, 참조하는 대상
의 값은 변경할 수 있다.
Lv4 트러블슈팅
각 클래스의 역할 구분
Menu 클래스에서 MenuItem 클래스를 관리해야 한다.
클래스가 4개가 되다보니 각 클래스의 역할이 정리가 되지 않아서 처음에 힘들었다.
Main클래스에서 생성한 Menu의 각 객체를 어떻게 Kiosk 클래스에 값을 넘겨줄지에 대해서 가장 큰 고민을 했다.
클래스간 흐름 설명 도식도(직접 그림)
// Menu 객체 생성을 통해 이름 설정
Menu burgers = new Menu("Burgers");
Menu drinks = new Menu("Drinks");
Menu desserts = new Menu("Desserts");
List<Menu> menuList = List.of(burgers,drinks,desserts);
// Menu 클래스 내 있는 List<MenuItem> 에 MenuItem 객체 생성하면서 삽입
MenuItem shackBurger = new MenuItem("ShackBurger",6.9,"토마토, 양상추, 쉑소스가 토핑된 치즈버거");
MenuItem SmokeShack = new MenuItem("SmokeShack",8.9,"베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거");
MenuItem Cheeseburger = new MenuItem("Cheeseburger",6.9,"포테이토 번과 비프패티, 치즈가 토핑된 치즈버거");
MenuItem Hamburger = new MenuItem("Hamburger",5.4,"비프패티를 기반으로 야채가 들어간 기본버거");
burgers.setMenuItems(List.of(shackBurger,SmokeShack,Cheeseburger,Hamburger));
// Kiosk 객체 생성
Kiosk kiosk = new Kiosk(menuList);
// Kiosk 내 시작하는 함수 호출
kiosk.start();
각 Menu 클래스의 참조값을 menuList라는 리스트에 넣어준 후,
Kiosk 클래스 객체 생성 시, 생성자에 menuList를 매개변수로 넘겨주었다.
public class Kiosk {
private final List<Menu> menuList;
public Kiosk(List<Menu> menuList) {
this.menuList = menuList;
}
/* 중략 */
}
Kiosk 클래스에서도 리스트를 생성하여
위에서 매개변수로 받은 menuList를 생성자를 통해 넣어주었다.
Lv5 트러블슈팅
사실 이전부터 캡슐화에 대해 신경쓰면서 구현했기 때문에
Lv4와 차이점이 없다.
25.1.16(목)
Lv6-1 트러블슈팅
switch문 -> if문으로 수정
필수 과제까지는 입력값에 따라 결과를 각각 다르게 출력해주는 조건문에 switch문을 사용했었다.
switch (inputSecondNumber) {
case 0:
System.out.println("뒤로 갑니다.");
break;
case 1:
menuItem = menuItems.get(0);
System.out.printf("1. %s | W %.1f | %s%n", menuItem.getName(),menuItem.getPrice(),menuItem.getInfo());
break;
case 2:
menuItem = menuItems.get(1);
System.out.printf("2. %s | W %.1f | %s%n", menuItem.getName(),menuItem.getPrice(),menuItem.getInfo());
break;
case 3:
menuItem = menuItems.get(2);
System.out.printf("3. %s | W %.1f | %s%n", menuItem.getName(),menuItem.getPrice(),menuItem.getInfo());
break;
case 4:
menuItem = menuItems.get(3);
System.out.printf("4. %s | W %.1f | %s%n", menuItem.getName(),menuItem.getPrice(),menuItem.getInfo());
break;
default:
throw new IllegalArgumentException("보기 중에 없는 번호이거나 숫자가 아닙니다. 다시 입력해주세요.");
}
그런데 도전 과제에서도 위와 같이 구현하기에는 어려운 점이 있었다.
왜냐하면 장바구니(cart) 기능이 추가되었기 때문이다.
필수 과제에서는 해당 메뉴 아이템을 선택하면 바로 주문하는 것으로 끝나면 되었다.
(메인 메뉴판 -> 메인 메뉴 선택 -> 선택한 메뉴의 상세 메뉴 출력 -> 상세 메뉴 선택 -> 해당 상세 메뉴 주문 완료)
하지만 도전 과제에서는 과정이 더 추가되어서 로직이 복잡해졌다.
(메인 메뉴판 -> 메인 메뉴 선택 -> 선택한 메뉴의 상세 메뉴 출력 -> 상세 메뉴 선택
-> 해당 메뉴 아이템 선택 -> 장바구니 추가 y/n -> y인 경우, 추가
-> 메인 메뉴판 (주문 관련 내용 추가 4,5번) -> 4번 클릭 시 장바구니 리스트 출력
-> 주문 여부 y/n -> y인 경우, 주문하고, 장바구니 리셋)
그렇다 보니 if문으로 오류 -> 종료 -> 정상로직 순서로 구현하게 되었다.
if (chooseMainMenu < 0 || chooseMainMenu > 5) {
System.out.println("보기 중에 없는 번호입니다. 다시 입력해주세요.");
sc.nextLine();
} else if (chooseMainMenu == 0) {
System.out.println("프로그램을 종료합니다.");
return;
} else if (chooseMainMenu == 4) {
if(cart.getCartList().size()!=0){
kiosk.orderMenu(cart, cartList);
int chooseOrder = sc.nextInt();
if (chooseOrder == 1) {
System.out.printf("주문이 완료되었습니다. 금액은 W %.1f 입니다.%n", cart.totalPriceCal(cartList));
cartList.removeAll(cartList);
} else if (chooseOrder == 2) {
sc.nextLine();
}
} else {
System.out.println("보기 중에 없는 번호입니다. 다시 입력해주세요.");
sc.nextLine();
}
} else if (chooseMainMenu == 5) {
if(cart.getCartList().size()!=0){
System.out.println("진행중인 주문이 취소되었습니다. 장바구니가 초기화 됩니다.");
cartList.removeAll(cartList);
} else {
System.out.println("보기 중에 없는 번호입니다. 다시 입력해주세요.");
sc.nextLine();
}
} else if (chooseMainMenu > 0 && chooseMainMenu < 4) {
Menu menu = menuList.get(chooseMainMenu - 1);
List<MenuItem> menuItems = menuList.get(chooseMainMenu - 1).getMenuItems();
if (menuItems == null){
System.out.println("해당 메인 메뉴의 상세 메뉴가 없습니다.");
continue;
} else {
System.out.println("[ " + menuList.get(chooseMainMenu - 1).showCategory().toUpperCase() + " MENU ]");
menu.showMenuItem();
}
if-else를 선택한 이유 요약
- 조건의 범위와 복잡한 비교를 처리해야 함.
- 유연한 조건 추가 및 수정이 가능.
- 예외 처리 및 부적합한 입력 처리를 포함.
- 유지보수 및 확장성이 더 좋음.
mvc 패턴
model view controller 패턴
이 패턴을 적용하고 싶었는데 생각만큼 잘 되지 않았다.
(아래 문제 참고)
Kiosk 클래스를 KioskView 와 KioskController 클래스로 분리하면서 발생한 문제점
-> 총 합계 금액이 0이 나오는 문제
[ Orders ]
ShackBurger | W 6.9 | 토마토, 양상추, 쉑소스가 토핑된 치즈버거
SmokeShack | W 8.9 | 베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거
[ Total ]
W 0.0
1. 주문 2. 메뉴판
2개로 분리를 하면서 문제가 발생했다.
KioskView 클래스에도 Cart 클래스의 인스턴스를 새로 생성한 것이 원인이었다.
Cart cart = new Cart();
그러다 보니 KioskController에서 생성한 Cart 클래스의 인스턴스와는 다른 새로운 객체를 생성하게 되었다.
장바구니 목록이 잘 나온 이유는 아래 코드를 보면 알 수 있다.
public void orderMenu(List<MenuItem> cartList) {
System.out.println("아래와 같이 주문 하시겠습니까?\n");
System.out.println("[ Orders ]");
for (MenuItem c : cartList) {
System.out.printf("%s | W %.1f | %s%n", c.getName(), c.getPrice(), c.getInfo());
}
System.out.println("\n[ Total ]");
System.out.printf("W %.1f%n",cart.totalPriceCal());
System.out.println("\n1. 주문 2. 메뉴판");
}
장바구니 목록은 장바구니 리스트를 controller에서 매개변수로 받아오기 때문에 출력이 잘 된다.
그러나, 총합계 계산 메서드의 경우에는 장바구니 리스트가 아닌 장바구니 클래스의 인스턴스의 메서드를 실행하는 것이라서
새로운 장바구니 클래스의 인스턴스는 당연히... 비어있어 초기화된 값을 출력했던 것이었다.
public void orderMenu(Cart cart, List<MenuItem> cartList) {}
그래서 위와 같이 controller에서 장바구니 클래스의 인스턴스도 같이 매개변수로 받아오게 했다.
남은 과제
튜터님께서 View에는 입출력(Scanner),
Controller에는 로직만 남기라고 하셨는데...
예외처리를 어느 정도 if문 안에서 처리하다보니, controller에도 scanner 클래스가 필요했다.
sc.nextLine() 남발하는 예외처리 이대로 괜찮은가...
if (chooseMainMenu < 0 || chooseMainMenu > 5) {
System.out.println("보기 중에 없는 번호입니다. 다시 입력해주세요.");
sc.nextLine();
}
25.1.17(금)
Lv6-1 트러블슈팅
MVC 패턴 적용 : KioskView와 KioskController 분리
어제 튜터님께서
View에는 입출력(Scanner),
Controller에는 각 조건에 맞게 처리하는 로직
이렇게 구현하라고 말씀해주셨다.
1) KioskController에서 사용하는 Scanner 처리방법
- Scanner에서 숫자를 입력 받아서 처리하는 로직
int chooseCart = sc.nextInt();
- if 문 안에서 예외처리로 sc.nextLine(); 사용 로직
if (chooseMainMenu < 0 || chooseMainMenu > 5) {
System.out.println("보기 중에 없는 번호입니다. 다시 입력해주세요.");
sc.nextLine();
}
위 2문제를 아래와 같이 해결했다.
KioskView kiosk = new KioskView();
KioskController에서 KioskView 객체를 생성한다.
int chooseCart = kiosk.chooseNumber();
KioskView 클래스의 메서드 반환값을 KioskController 변수에 넣는다.
// Scanner 선언
Scanner sc = new Scanner(System.in);
public int chooseNumber() {
return sc.nextInt();
}
public void nextLine(){
sc.nextLine();
}
KioskView 클래스에 메서드로 분리하여 사용하는 것이다.
2. NullPointerException 예외 처리
메인 메뉴는 3가지이지만, 메뉴 아이템(상세메뉴)은 햄버거 밖에 없다.
따라서 나머지 음료와 디저트 메인 메뉴를 선택할 경우, NullPointerException이 발생한다.
팀원분께서 NullPointerException은
try-catch문으로 예외처리하는 것보다는
사전에 해당 오류가 발생하지 않도록 처리하는 것이 더 좋다고 하셨다.
따라서 catch문 -> if 문 안으로 넣어서 처리했다.
if (menuItems != null){
System.out.println("해당 메인 메뉴의 상세 메뉴가 없습니다.");
continue;
}
튜터님께서 ObjectUtils.isEmpty()를 추천해주셨다.
empty인지 확인하는 메서드인데
좋은 점은 Null 과 size가 0인지 같이 확인하는 점이 장점이다.
그런데 ObjectUtils 클래스를 사용하려고 하는데, 자동으로 import가 되지 않는다.
찾아보니 의존성 주입을 해야 한다고 한다.
그래서 build.gradle에 들어가서 아래와 같이 추가해주었다.
dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
}
if (ObjectUtils.isEmpty(menuItems)){
System.out.println("해당 메인 메뉴의 상세 메뉴가 없습니다.");
continue;
}
이렇게 추가해주면 위의 조건문이 잘 실행된다.
Lv6-2 트러블슈팅
Enum을 활용해 사용자 유형에 맞게 할인율 적용
팀원분께서 할인율도 같이 Enum 값에 넣으면 좋겠다고 제안해주셨다.
public enum User {
VETERAN(1,10),
SOILDER(2,5),
STUDENT(3,3),
GENERAL(4,0);
//값을 저장할 필드(인스턴스 변수)
private final int value; //순서
private final double discountRate; //할인율
}
위와 같이 각 Enum에 value와 할인율을 각각 저장해주었다.
// userDiscount : 사용자의 할인율에 따라 총 합계를 계산해서 출력하는 함수
public double userDiscount(Cart cart, int chooseUser) {
User user = User.findByVal(chooseUser);
double totalPrice = cart.totalPriceCal();
switch (user) {
case VETERAN:
totalPrice -= totalPrice * (User.VETERAN.getDiscountRate() / 100);
break;
case SOILDER:
totalPrice -= totalPrice * (User.SOILDER.getDiscountRate() / 100);
break;
case STUDENT:
totalPrice -= totalPrice * (User.STUDENT.getDiscountRate()/ 100);
break;
case GENERAL:
totalPrice -= totalPrice * (User.GENERAL.getDiscountRate()/ 100);
break;
default:
throw new IllegalStateException("잘못된 번호입니다.");
}
System.out.printf("주문이 완료되었습니다. 금액은 W %.2f 입니다.%n", totalPrice);
return totalPrice;
}
이렇게 하면 기존에 총합계 계산식에
10%, 5%, 3%, 0%와 같은 숫자를 곱하는 것이 아니라
각각에 해당하는 Enum의 할인율을 가져와서 사용하니까 더 직관적이다.
// enum의 값이 매개변수와 같으면 enum의 값을 리턴합니다.
public static User findByVal(int value) {
User[] values = User.values();
for (User v : values) {
if (value ==v.value) {
return v;
}
}
throw new IllegalStateException("잘못된 번호입니다.");
}
또한 기존에 return null;을 처리해서 생기는 NullPointerException은
예외처리를 throw로 처리해주었다.
람다 & 스트림을 이용해 장바구니 목록 삭제하기 기능 구현
나의 경우에는 5번 Cancel을 선택하면
장바구니 목록 중 선택한 번호의 메뉴 아이템이 삭제되도록 구현하고자 했다.
[ ORDER MENU ]
4. Orders
5. Cancel
5
장바구니 목록
1. Hamburger | W 5.4 | 비프패티를 기반으로 야채가 들어간 기본버거
2. ShackBurger | W 6.9 | 토마토, 양상추, 쉑소스가 토핑된 치즈버거
삭제할 메뉴 아이템을 선택하세요.
2
ShackBurger 해당 메뉴가 장바구니에서 삭제되었습니다.
장바구니 목록
1. Hamburger | W 5.4 | 비프패티를 기반으로 야채가 들어간 기본버거
먼저 내가 구현하고 싶은 코드를 람다, 스트림을 사용하지 않고 작성해보았다.
그러다가 아래와 같은 예외가 발생했다.
Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967) at com.example.kiosk.level6.model.Cart.removeMenuItem(Cart.java:33) at com.example.kiosk.level6.Controller.KioskController.start(KioskController.java:68) at com.example.kiosk.level6.Main.main(Main.java:30)
출처 : https://jyyoun1022.tistory.com/12
순회중에 값을 제거했기 때문에
맨 마지막 목록의 값을 제거하면 위와 같은 Exception이 나오게 된다.
배열이나 리스트에 자주 사용하는 for each문 -> for문으로 수정해서 구현했다.
//stream 활용 전 코드 작성 - 주석처리
for (int i = 0 ;i<cartList.size();i++) {
MenuItem cartItem = cartList.get(i);
if(cartList.indexOf(cartItem)+1 == chooseMenuItemNumber){
cartList.remove(cartList.indexOf(cartItem));
System.out.println(cartItem.getName()+" 해당 메뉴가 장바구니에서 삭제되었습니다.\n");
}
}
위의 내용을 람다와 스트림을 활용해서 작성하기
cartList.stream()
.filter(cartItem -> cartList.indexOf(cartItem) + 1 == chooseMenuItemNumber)
.findFirst()
.ifPresent(cartItem -> {
cartList.remove(cartItem);
System.out.println(cartItem.getName() + " 해당 메뉴가 장바구니에서 삭제되었습니다.\n");
});
.filter(cartItem -> cartList.indexOf(cartItem) + 1 == chooseMenuItemNumber):
- 스트림의 각 요소(cartItem)에 대해 필터링을 수행
- cartList.indexOf(cartItem) : 현재 cartItem의 인덱스를 반환
- 여기에 1을 더해 선택한 메뉴 번호와 일치시킴(인덱스는 0부터 시작하므로).
- 위의 값이 사용자가 선택한 메뉴 번호(chooseMenuItemNumber)와 일치하는지 확인
- 조건이 참인 요소만 다음 단계로 전달
.findFirst():
- 필터링된 결과 중 첫 번째 요소를 선택
- Optional 객체를 반환
(값이 존재할 수도, 존재하지 않을 수도 있음을 나타냄)
.ifPresent(cartItem -> { ... }):
- Optional 객체에 값이 존재하는 경우에만 내부의 람다 표현식을 실행
- 값이 없으면(해당 번호의 메뉴가 없으면) 아무 동작도 하지 않음
람다...스트림...
익숙하지 않아 다양한 예제를 접하면서 익혀야 할 것 같다.
그래도 두 번째 개인 과제라서 그런지
첫 번째 과제보다 더 짧은 시간이 주어졌는데도 불구하고,
여기까지 잘 올 수 있게 된 것 같아 기쁘다. :)
'CODING > 스파르타 내일배움캠프 TIL' 카테고리의 다른 글
26_없는 숫자 더하기_키오스크 과제 6-1, 2 구현 Enum, 람다&스트림_25.1.17(금) (0) | 2025.01.17 |
---|---|
25_핸드폰 번호 가리기_도전과제 레벨 6-1 구현_자바 강의 String_25.1.16(목) (1) | 2025.01.16 |
24_음양 더하기_자바 강의 다형성, Object_25.1.15(수) (0) | 2025.01.15 |
23_나누어 떨어지는 숫자 배열_키오스크 필수과제 레벨 3,4,5 구현_계산기 과제 피드백 내용_25.1.14(화) (0) | 2025.01.14 |
22_서울에서 김서방 찾기_키오스크 필수과제 레벨 1,2 구현_객체지향 강의_25.1.13(월) (0) | 2025.01.13 |