본문 바로가기
개인 프로젝트/kiosk-project

State pattern <2>

by pon9 2024. 11. 24.

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%83%81%ED%83%9CState-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

 

💠 상태(State) 패턴 - 완벽 마스터하기

State Pattern 상태 패턴(State Pattern)은 객체가 특정 상태에 따라 행위를 달리하는 상황에서, 상태를 조건문으로 검사해서 행위를 달리하는 것이 아닌, 상태를 객체화 하여 상태가 행동을 할 수 있도

inpa.tistory.com

https://velog.io/@rudnf003/Java-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4State-Pattern

 

[Java] 상태 패턴(State Pattern)

❗ 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴의 내용을 정리하였으나, 실제로 프로그램을 작동시키기 위해 일부 코드를 수정하였습니다. 간단한 자판기 기능을 하는 프로그램을 구

velog.io

이 두개의 글을 많이 참고했다.

 

상태 패턴이란 쉽게 말해서 "상태"를 클래스로 표현해, 이것을 교체하며 "상태의 변화"를 나타낼 수 있고, 각각의 상태들이 서로에게 영향을 주지 않는다.

그러므로 상태 클래스는 싱글톤 클래스로 구성되어야 한다. 상태는 그 객체의 현 form을 나타내는 것이기 때문에 유일해야 한다.

 

키오스크의 주문과정별로 클래스를 만들고 상태를 객체화해보자.

원래같으면 switch case문이나 if문으로 코드가 아주 복잡해질 것인데, 상태 패턴을 사용하면 가독성도 좋고 유지보수도 용이하다.

 

단순히 틀을 만들기는 쉽다. 키오스크의 경우 주문상태 인터페이스를 만들고, 각각의 상태에 그것을 상속시키고, 상태 전환을 관리하는 역할을 하는 클래스에서 모든 상태 변경을 총관리한다.

interface다.

handleinput은 사용자 입력을 처리하고, 입력값에 따라 exception을 출력하거나 다른 상태로 전환한다.

displaymenu는 각각 상태에서의 선택지를 콘솔에 출력한다.

state class

모든 state 클래스가 orderstate를 상속받으므로 이렇게 복붙해뒀다.

instance는 싱글톤 패턴을 구현하기 위해 사용했다. 클래스의 인스턴스를 하나만 생성해 어디에서나 접근할 수 있도록 해준다.

getInstance를 통해, 서로 다른 클래스에서 상태를 전환할 때 새로운 객체를 생성하지 않아도 된다.

일 너무 크게 벌렸는데 또?

곰곰히 생각해보니 taste와 completed는 쓸데없이 들어간 상태 같아서 지웠다. 각각 mainmenu와 payment에서 구현 가능할 것 같다. 이래놓고 또 만들 수도 있다

관리자 역할을 하는 친구다. 이곳의 핵심은 currentState다.

각각의 state들은 setCurrentState를 이용해 다른 상태로 변할 수 있다.

displaymenu로 메인이 되는 콘솔창을 띄우고, 입력받은 값을 handleinput으로 보내 각종 동작을 수행한다.(setCurrentState 등..)

 

이제 모든 프로그램의 시작점인 mainmenustate부터 구현해보자.

package State;

import Exception.BadInputException;
import Menu.Main.MainMenuFactory;
import Menu.Main.MainMenuType;

public class MainMenuState implements OrderState {

    private static final MainMenuState instance = new MainMenuState();

    public static MainMenuState getInstance(){
        return instance;
    }

    @Override
    public void handleInput(StateHandler status, String input) {
        try{
            int choice = Integer.parseInt(input);

            if(choice > 0 && choice <= MainMenuType.values().length){
                MainMenuType type = MainMenuType.values()[choice-1];
                System.out.println("\n\n" + type.getName() + "을(를) 선택했습니다.");

                System.out.println("맛을 선택하세요:");
                System.out.println("1. 착한맛");
                System.out.println("2. 초보맛");
                System.out.println("3. 약간매운맛");
                System.out.println("4. 보통맛");
                System.out.println("5. 매운맛");
                System.out.println("8. 메인 메뉴 다시보기");

                String tasteInput = new java.util.Scanner(System.in).nextLine();
                int tasteChoice = Integer.parseInt(tasteInput);
                int taste = switch(tasteChoice){
                    case 1 -> 1;
                    case 2 -> 2;
                    case 3 -> 3;
                    case 4 -> 4;
                    case 5 -> 5;
                    case 6 -> 6;
                    case 7 -> 7;
                    case 8 -> 8;
                    default -> throw new BadInputException(".");
                };
                if(taste >= 1 && taste <= 5){
                    status.getCart().addMainMenuItem(MainMenuFactory.createMainMenu(type, 1, taste));
                    System.out.println("\n\n장바구니에 메뉴가 추가되었습니다.");
                }
            }else if(choice == 6){
                status.setCurrentState(SideMenuState.getInstance());
            }else if(choice == 7){
                status.setCurrentState(CartState.getInstance());
            }else if(choice == 8){
                displayMenu(status);
            }else{
                throw new BadInputException("잘못된 입력입니다. 다시 시도해주세요");
            }

        }catch(BadInputException e){
            System.out.println("잘못된 입력입니다. 다시 시도해주세요");
        }
    }

    @Override
    public void displayMenu(StateHandler status) {
        int index = 1;

        System.out.println("\n\n메인메뉴");
        for(MainMenuType type : MainMenuType.values()){
            System.out.println(index++ + ". " + type.getName() + " | W " + type.getPrice() + " | " + type.getDescription());
        }
        if(!status.isCartEmpty()){
            System.out.println("\n6. 사이드 메뉴 선택");
            System.out.println("7. 장바구니로 이동");
        }
        System.out.println("\nexit. 키오스크 종료");
        System.out.println("\n번호를 입력하세요:");
    }
}

sout 노가다를 했다. 뇌 빼고 하다가 switch case 잘못 써서 몇번을 고쳤는지 모르겠다 ㅋㅋ

구현하다 보니 displayMenu도 status를 파라미터로 가지게 되었다. 그리고 역시 taste는 여기서 구현하는 것이 맞았다.

state로 만들어두고 다른 코드를 고치며 느끼는 점인데 정말 정말 정말 편하다.

상태 패턴의 핵심은 모든 state가 statehandler에서 보내주는 status에 따라서만 독립적으로 작동하기 때문에,

세부 state를 건들 필요 없이 statehandler에서 굵직한 걸 다 처리하니 자잘한 것만 고치면 되는 수준이다.(함수명이라던가)

다닥다닥 붙어나오니 보기에 좀 별로라 ui도 좀 신경써줬다.ㅎ

 

그러니까 이제 이런걸 6개정도만 더 만들면 된다는거네?

'개인 프로젝트 > kiosk-project' 카테고리의 다른 글

Trouble shooting <4>  (0) 2024.11.25
Builder pattern<3>  (1) 2024.11.24
kiosk 구조 짜기 <1>  (1) 2024.11.23