본문 바로가기

JAVA

키오스크에 장바구니 및 구매하기 기능 추가하기 (+반성의 글..)

결론부터 말하자면 이번 과제는 많이 부족했던 것 같다🙈

하지만 어쩌겠는가,,, 이미 시간은 지났고 되돌릴 수 없다

지금부터 고민했던 점들에 대해서 정리해보도록 하고 다음 프로젝트는 이번 문제점을 고쳐서 발전시키는 방향으로 가자😳😳😳

 

고민점

장바구니 기능을 클래스로 빼는게 맞는지에 대한 고민

  • 장바구니기능이 없는 lv5만 구현했을 때까지만 하더라도 Kiosk 클래스는 사용자의 주문에 대한 입출력에 대해서 기능적인 부담이 크지 않다고 생각
  • 하지만, 장바구니 요구사항을 보니 내용이 꽤 많았음.. (추가 삭제 조회 등등, + 예외 처리)
  • 그렇기 때문에 이 모든 요구사항을 Kiosk에 넣어버리면 코드도 엄청 무거워지고 알아보기가 너무 힘들 것 같다고 생각

-> 장바구니와 관련된 역할은 Cart 클래스에서 하기로 결정!

public class Cart {
    private List<MenuItem> cartItems = new ArrayList<>(); // 장바구니에 넣은 물건을 담는 리스트
    private double total;
    private Scanner sc = new Scanner(System.in);

    public void addCart(MenuItem menuItem){
    // 장바구니에 물건 추가
    }

    public void showCartItems() {
	// 장바구니에 담긴 물건 조회
    }

    public void showTotalCost() {
    // 장바구니 물건 합계 출력
    }

    public void showCartAddMsg(MenuItem menuItem) {
    // 장바구니에 메뉴를 추가할지 묻는 메서드
    }

    public void deleteCartItem() throws MenuItemNotFoundException{
    // 장바구니 메뉴 삭제
    }

    public void cancelCart() {
    // 장바구니 물건 전체 삭제
    }

    public double getTotal() {
    }

    public boolean isCartEmpty(){
    }

}

 

 

작은 기능들까지도 메서드로 분리하는게 맞는 것인지에 대한 고민 + 여전한 Kiosk의 막대한 책임

  • 요구조건에 맞게 구현하려다 보니 start()메서드가 너무 방대하고 복잡해져 버림
  • 더군다나 출력에 관한 것도 너무 많고 지져분해져서 개발을 하면서도 너무 복잡하게 느껴짐 (아래보다 훠얼씬 많음 ㅠ_ㅜ)

  • 그러한 이유로 먼저 출력에 관한 메서드들을 생성해서 분리시킴 -> 확실히 가독성 측면에서 훨씬 깔끔했고, start()메서드의 길이가 줄어서 개발할 때 중요도가 낮은 출력 메서드들의 내부 로직을 볼 필요가 없어서 한결 편했음

하지만 이렇게 자잘하게 메서드를 분리해서 만드는 것이 과연 옳은지 고민이 된다...

역할이라고 하기엔 너무 하찮지 않나 싶은 생각도 들고, 어쩌면 태초로 돌아가서 객체지향 설계가 잘못된 것 같다는 생각이 들기도 했다

키오스크 클래스에 모든 책임들이 너무 방대하게 있다는 생각이 쉽사리 지워지지 않았다

 

메서드 분리 후에도 여전한 문제점

  • 주석 없이도 코드에 대해서 이해하고 파악할 수 있어야 하는데 도통 주석 없이는 이해하기 힘들다는 생각
  • 메서드로 최대한 많이 분리했지만 결국 start메서드 자체는 여전히 복잡함
  • Kiosk 의 책임은 여전히 너무나 큼

설계의 잘못됨(?)

하지만 또 생각해보면..

사용자에게 메뉴를 보여주고 입력을 받고 흐름을 제어하는 것 => Kiosk

장바구니와 관련된 역할을 수행하는 것 => Cart

가 아닐까 생각이 들기도 한다. 아니면 역할을 애초에 사용자의 키오스크 화면 상에서 카테고리와 관련된 로직을 수행하는 클래스, 메뉴와  관련된 로직을 수행하는 클래스, 장바구니와 관련된 로직을 수행하는 클래스 이런식으로 3개를 나누어서 설계를 했어야 하나 싶은 고민이 들기도 한다...

이번 키오스크 프로젝트에서 과연 객체지향적으로 적절하게 설계한 답이 너무 궁금하다 (나오면 바로 분석해야지)

 

 

초기화 메서드를 최초 한번만 실행하기 위한 방법에 대한 고민

  • menuItems의 리스트 인덱스를 생성을 딱 한번만 하기 위해서 어떤 식으로 할지 고민하다가 showMenuItems 메서드를 실행할 때 리스트 인덱스가 비어있으면 생성하고, 비어있지 않다면 생성하지 않는 식으로 구현
public void showMenuItems(){
        if (menuItemIndexList.isEmpty()){
            setMenuItemIndex();
        }
        for(int i = 0; i < menuItems.size(); i++){
            System.out.println( i + 1 + ". " + menuItems.get(i).getName() + " | W " + menuItems.get(i).getPrice() + " | " + menuItems.get(i).getDescription());
        }
    }

    public void setMenuItemIndex() {
        for(int i = 0; i < menuItems.size(); i++){
            menuItemIndexList.add(i+1);
        }
    }

 

그 밖의 메모해뒀던 여러 잡다한 고민들...

하지만 내가 생각한 이 고민들의 근원은 미약한 설계 이다.

결국 클래스 설계부터 기타 로직까지 충분한 시간을 가지고 생각하고 구현을 했어야 했는데 그냥 냅다 기존에 진행했던 과제를 기반으로 신나게 기능들만 열심히 구현을 해서 이런 생각이 든 것 같다. (그래서 막상 코드가 너무 복잡하고 더러워졌다. 그냥 절차지향의 코드인듯)

알고리즘을 풀 때도 종이랑 펜으로 먼저 설계라고 코딩을 하듯이 앞으로 프로젝트도 설계를 좀 더 주의깊게 한 다음에 코딩을 시작해야겠다!

 

Enum과 할인 적용

  • number(할인 정보 인덱스 번호), type(할인 타입), discountRate(할인률)로 구성
  • 할인을 적용하는 메서드(discount)는 추상메서드로 두고, 각각의 할인 객체는 이를 오버라이딩
  • 할인 정보를 출력하는 메서드(showInformation)와 사용자로부터 입력받은 값이 어떤 할인 객체에 해당되는지 확인하는 메서드(fromDiscount)는 정적(static)으로 선언 
  • fromDiscount 메서드에서 해당되는 할인 객체를 찾으면 해당 객체에서 discount를 실행한 후, 값을 바로 return
package org.example.kiosk.challenge;

import org.example.kiosk.common.exception.InvalidInputRangeException;

public enum Discount {
    NATIONALHONOREE("1", "국가유공자", 0.1) {
        @Override
        public double discount(double total) {
            return total - total * getDiscountRate();
        }
    },
	// 군인..학생..등등

    private final String number; // 할인 정보 인덱스 번호
    private final String type; // 할인 타입
    private final double discountRate; // 할인률

    public String getNumber() {
        return number;
    }

    public String getType() {
        return type;
    }

    public double getDiscountRate() {
        return discountRate;
    }

    Discount(String number, String type, double discountRate) {
        this.number = number;
        this.type = type;
        this.discountRate = discountRate;
    }

    // 각 클래스에 맞게 할인률이 적용되어 할인을 진행할 메서드
    public abstract double discount(double total);

    // 입력받은 문자열과 매칭되는 클래스인지 확인 후, 해당 할인 클래스의 할인을 수행시킨 후 적용된 할인값을 return
    public static double fromDiscount(String inputNumber, double total) throws InvalidInputRangeException{
        for (Discount discount : values()) {
            if (discount.getNumber().equals(inputNumber)) {
                return discount.discount(total);
            }
        }
        // 해당 할인 정보 화면에서 존재하지 않는 값을 입력했다면 에러 발생
        throw new InvalidInputRangeException();
    }

    // 할인 정보를 출력하는 메서드, 굳이 인스턴스화 시킬 필요가 없을 것 같아서 static으로 선언
    public static void showInformation(){
    }
}

 

Exception

  • InvalidInputException : 숫자가 아닌 다른 값을 입력할 경우 발생하는 예외 객체
  • InvalidInputRangeException : 화면에 출력된 값이 아닌 다른 숫자를 입력할 경우 발생하는 예외 객체
  • MenuItemNotFoundException : 메뉴를 삭제할 때, 장바구니 리스트에 없는 이름을 입력한 경우 발생하는 예외 객체

 

 


다시 결론적으로 앞으로는 설계를 충분히 고민하고 구현을 시작하자!!