디자인 패턴

State Pattern

kyoulho 2024. 6. 15. 15:27

상태 패턴은 객체가 내부적으로 상태를 변경할 때마다 객체의 행동을 바꾸는 패턴이다.
이 패턴은 객체의 상태에 따라 객체가 수행하는 동작을 동적으로 변경할 수 있게 한다.

 

상태 패턴을 사용해야 하는 상황

객체의 행동이 상태에 따라 변할 때 객체의 행동이 특정 상태에 의존적이고,
상태가 변경될 때마다 객체의 행동이 동적으로 변해야 할 때 유용하다.
복잡한 조건문 대체 상태에 따른 각각의 동작을 개별적인 상태 클래스로 캡슐화하면
복잡한 조건문을 피할 수 있다.
유집보수 용이성 새로운 상태가 추가되거나 기존 상태의 동작이 변경될 때,
해당 상태에 대한 클래스만 수정하면 되므로 유지보수가 용이하다.

 

상태 패턴을 사용하는 경우의 문제점

클래스 수 증가 각 상태를 별도의 클래스로 구현하기 때문에 클래스의 수가 증가한다
상태 전환 오버헤드 상태가 변경될 때마다 새로운 상태 객체를 생성해야 하므로 객체 생성 및 소멸에 따른 부하가 발생한다.
순환 참조 상태 객체 간에 순환 참조가 발생할 수 있으며 메모리 누수로 이어질 수 있다
상태 전환 로직의 중앙 집중화 상태 전환 로직이 클라이언트나 컨텍스트 클래스에 직접 구현되는 경우가 있을 수 있다.
이로 인해 상태 패턴의 장점이 퇴색되고, 전환 로직의 중앙 집중화로 복잡성이 높아질 수 있다.

 

 

// GumballMachine의 상태 인터페이스. 각 상태는 이 인터페이스를 구현하여 구체적인 동작을 정의한다.
public interface State {
    void insertQuarter(); // 동전을 삽입하는 동작
    void ejectQuarter(); // 동전을 반환하는 동작
    void turnCrank(); // 손잡이를 돌리는 동작
    void dispense(); // 껌볼을 내보내는 동작
}

// GumballMachine 클래스는 껌볼 머신의 다양한 상태를 관리한다.
public class GumballMachine {

    // 상태와 해당 State 인스턴스를 매핑하는 맵
    private final Map<StateEnum, State> stateMap = Map.of(
            StateEnum.SOLD_OUT, new SoldOutState(this),
            StateEnum.NO_QUARTER, new NoQuarterState(this),
            StateEnum.HAS_QUARTER, new HasQuarterState(this),
            StateEnum.SOLD, new SoldState(this)
    );

    private State state; // 현재 상태
    private int count; // 껌볼 개수

    // 생성자: 껌볼 개수를 받아 초기 상태를 설정
    public GumballMachine(int numberGumballs) {
        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = stateMap.get(StateEnum.NO_QUARTER); // 동전 없음 상태로 초기화
        } else {
            state = stateMap.get(StateEnum.SOLD_OUT); // 품절 상태로 초기화
        }
    }

    // 동전을 삽입하는 메서드
    public void insertQuarter() {
        state.insertQuarter();
    }

    // 동전을 반환하는 메서드
    public void ejectQuarter() {
        state.ejectQuarter();
    }

    // 손잡이를 돌리는 메서드
    public void turnCrank() {
        state.turnCrank();
        state.dispense(); // 손잡이를 돌린 후 알맹이를 내보낸다.
    }

    // 알맹이를 내보내는 메서드
    public void releaseBall() {
        System.out.println("알맹이를 내보내고 있습니다.");
        if (count > 0) {
            count = count - 1;
        }
    }

    // 현재 상태를 변경하는 메서드
    public void changeState(StateEnum stateEnum) {
        this.state = stateMap.get(stateEnum);
    }

    // 껌볼 개수를 반환하는 메서드
    public int getCount() {
        return this.count;
    }
}


// GumballMachine의 상태를 나타내는 열거형. 각 상태는 별도의 클래스로 구현된다.
public enum StateEnum {
    SOLD, // 판매 중
    SOLD_OUT, // 품절
    NO_QUARTER, // 동전 없음
    HAS_QUARTER // 동전 있음
}

// GumballMachine의 '판매 중' 상태를 나타내는 클래스.
public class SoldState implements State {
    private final GumballMachine gumballMachine;

    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("알맹이를 내보내고 있습니다.");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("이미 알맹이를 뽑았습니다.");
    }

    @Override
    public void turnCrank() {
        System.out.println("알맹이를 내보내고 있습니다.");
    }

    @Override
    public void dispense() {
        gumballMachine.releaseBall(); // 알맹이를 내보낸다
        if (gumballMachine.getCount() > 0) {
            gumballMachine.changeState(StateEnum.NO_QUARTER); // 동전 없음 상태로 변경
        } else {
            System.out.println("알맹이가 없습니다.");
            gumballMachine.changeState(StateEnum.SOLD_OUT); // 품절 상태로 변경
        }
    }
}

// GumballMachine의 '품절' 상태를 나타내는 클래스.
public class SoldOutState implements State {
    private final GumballMachine gumballMachine;

    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("알맹이가 없습니다."); // 품절 상태에서는 동전을 삽입할 수 없습니다.
    }

    @Override
    public void ejectQuarter() {
        System.out.println("알맹이가 없습니다."); // 반환할 동전이 없습니다.
    }

    @Override
    public void turnCrank() {
        System.out.println("알맹이가 없습니다."); // 손잡이를 돌려도 알맹이가 나오지 않습니다.
    }

    @Override
    public void dispense() {
        System.out.println("알맹이가 없습니다."); // 내보낼 알맹이가 없습니다.
    }
}

// GumballMachine의 '동전 없음' 상태를 나타내는 클래스.
public class NoQuarterState implements State {
    private final GumballMachine gumballMachine;

    NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("동전을 넣으셨습니다.");
        gumballMachine.changeState(StateEnum.HAS_QUARTER); // 동전 있음 상태로 변경
    }

    @Override
    public void ejectQuarter() {
        System.out.println("동전을 넣어 주세요.");
    }

    @Override
    public void turnCrank() {
        System.out.println("동전을 넣어 주세요.");
    }

    @Override
    public void dispense() {
        System.out.println("동전을 넣어 주세요.");
    }
}

// GumballMachine의 '동전 있음' 상태를 나타내는 클래스.
public class HasQuarterState implements State {
    private final GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("동전은 한 개만 넣어 주세요.");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("동전이 반환됩니다.");
        gumballMachine.changeState(StateEnum.NO_QUARTER); // 동전 없음 상태로 변경
    }

    @Override
    public void turnCrank() {
        System.out.println("손잡이를 돌리셨습니다.");
        gumballMachine.changeState(StateEnum.SOLD); // 판매 중 상태로 변경
    }

    @Override
    public void dispense() {
        System.out.println("알맹이를 내보낼 수 없습니다.");
    }
}

'디자인 패턴' 카테고리의 다른 글

template method pattern  (0) 2024.07.05
Singletone Pattern  (0) 2024.06.15
Proxy Pattern  (0) 2024.06.15
Observer Pattern  (0) 2024.05.30
Mediator Pattern  (0) 2024.05.28