티스토리 뷰

상태 패턴이란?

상태패턴은 객체가 특정 상태에 따라 행위를 다르게 할 때, 직접 상태를 조건문으로 검색해서 행위를 다르게 하는 것이 아니라 상태를 객체화해 상태가 행동을 할 수 있도록 위임하는 패턴이다.

상태란 객체가 가질 수 있는 어떤 조건이나 상황을 의미한다.
예를 들어 티비가 켜져 있는 상태라면 음량 버튼을 누르면 음량이 증가하거나 감소한다. 하지만 티비가 꺼져있는 상태라면 음량버튼을 아무리 눌러도 티비의 음량은 바뀌지 않는다. 즉, 티비 전원의 상태에 따라 메소드 행동이 바뀌는 것이다. 이처럼 객체가 특정 상태에 따라 행위를 달리하는 상황에서 사용되는 최적의 패턴이 스테이트 패턴이다.

상태 패턴의 사용 법

  • State 인터페이스 : Context 객체의 현재 상태에 따라 다른 행위를 하는 함수들의 인터페이스를 정의한다.
  • ConcreteState : 구체적인 각각의 상태를 클래스로 표현. State 역할로 결정되는 인터페이스(API)를 구체적으로 구현한다. 다음 상태가 결정되면 Context에 상태 변경을 요청하는 역할도 한다.
  • Context : State를 이용하는 시스템. 시스템 상태를 나타내는 State 객체를 합성(composition)하여 가지고 있다. 클라이언트로부터 요청받으면 State 객체에 행위 실행을 위임한다.

//State Interface
interface PowerState {
    void powerButtonPush(LaptopContext cxt);

    void typebuttonPush();
}
//구체적인 상태 클래스
class OffState implements PowerState {

    private OffState(){}
    private static class SingleInstanceHolder{
        private static final OffState INSTANCE=new OffState();
    }

    public static OffState getInstance(){
        return SingleInstanceHolder.INSTANCE;
    }
    @Override
    public void powerButtonPush(LaptopContext cxt) {
        System.out.println("노트북 전원 ON");
        cxt.changeState(OnState.getInstance());
    }

    @Override
    public void typebuttonPush() {
        throw new IllegalStateException("노트북이 OFF 인 상태");
    }

    @Override
    public String toString() {
        return "노트북이 전원 OFF 인 상태 입니다.";
    }
}

class OnState implements PowerState {

    private OnState(){}
    private static class SingleInstanceHolder{
        private static  final OnState INSTANCE=new OnState();
    }

    public static OnState getInstance(){
        return SingleInstanceHolder.INSTANCE;
    }
    @Override
    public void powerButtonPush(LaptopContext cxt) {
        System.out.println("노트북 전원 OFF");
        cxt.changeState(OffState.getInstance());
    }

    @Override
    public void typebuttonPush() {
        System.out.println("키 입력");
    }

    @Override
    public String toString() {
        return "노트북이 전원 ON 인 상태 입니다.";
    }
}
class SavingState implements PowerState {

    private SavingState(){}
    private static class SingleInstanceHolder{
        private static final SavingState INSTANCE=new SavingState();
    }
    public static SavingState getInstance(){
        return SingleInstanceHolder.INSTANCE;
    }

    @Override
    public void powerButtonPush(LaptopContext cxt) {
        System.out.println("노트북 전원 on");
        cxt.changeState(OnState.getInstance());
    }

    @Override
    public void typebuttonPush() {
        throw new IllegalStateException("노트북이 절전 모드인 상태");
    }

    @Override
    public String toString() {
        return "노트북이 절전 모드 인 상태 입니다.";
    }
}
class LaptopContext {
    PowerState powerState;

    LaptopContext() {
        this.powerState = OffState.getInstance();
    }

    void changeState(PowerState state) {
        this.powerState = state;
    }

    void setSavingState() {
        System.out.println("노트북 절전 모드");
        changeState(SavingState.getInstance());
    }

    void powerButtonPush() {
        powerState.powerButtonPush(this);
    }

    void typebuttonPush() {
        powerState.typebuttonPush();
    }

    void currentStatePrint() {
        System.out.println(powerState.toString());
    }
}
class Client {
    public static void main(String[] args) {
        LaptopContext laptop = new LaptopContext();
        laptop.currentStatePrint();

        // 노트북 켜기 : OffState -> OnState
        laptop.powerButtonPush();
        laptop.currentStatePrint();
        laptop.typebuttonPush();

        // 노트북 절전하기 : OnState -> SavingState
        laptop.setSavingState();
        laptop.currentStatePrint();

        // 노트북 다시 켜기 : SavingState -> OnState
        laptop.powerButtonPush();
        laptop.currentStatePrint();

        // 노트북 끄기 : OnState -> OffState
        laptop.powerButtonPush();
        laptop.currentStatePrint();
    }
}

 

상태 객체를 하나만 생성해야 될 때 싱글톤 패턴을 사용하고, 
여러 종류의 State 객체들을 공유해야 할 때 플라이웨이트 패턴을 활용할 수 있다.

패턴 사용 시기

  • 객체의 메소드가 상태에 따라 각기 다른 동작을 할 때
  • 상태 및 전환에 걸쳐 대규모 조건 분기 코드와 중복 코드가 많을 경우

패턴 장점

  • 상태에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있다.
  • 상태와 관련된 모든 동작을 각각의 상태 클래스에 분산시킴으로써, 코드 복잡도를 줄일 수 있다.
  • 모든 상태의존 코드가 상태 서브클래스로 존재하기 때문에 새로운 상태를 추가하여 구현하기 용이하므로 OCP원칙을 준수한다.

패턴 단점

  • 상태 별로 클래스를 생성해야하므로, 관리해야할 클래스 수가 증가한다.
  • 상태 클래스 개수가 많고 상태 규칙이 자주 변경된다면 Context의 상태 변경 코드가 복잡해지게 될 수도 있다.