[Java] 스테이트 패턴

스테이트 패턴

객체에 상태에게 역할을 위임하는 스테이트 패턴을 알아보자.

객체의 상태

객체에서의 상태란 객체가 생존 주기 동안 가질 수 있는 조건이나 상황을 뜻한다.
사람이나 사물도 현재에 상태에 따라 다르듯이, 객체지향도 현실 세계를 반영한 것이기 때문에 객체마다 상태가 존재한다.

Switch & Client

Switch

public class Switch {
    private int ON = 1;
    private int OFF = 0;

    private int state;
    public Switch(){
        state =  OFF;
    }

    public void getOnSwich(){
        if(state==ON) System.out.println("이미 ON 상태입니다.");
        else{
            state=ON;
            System.out.println("스위치가 켜졌습니다.");
        }
    }

    public void getOffSwitch(){
        if(state==OFF) System.out.println("이미 OFF 상태입니다.");
        else{
            state=ON;
            System.out.println("스위치가 꺼졌습니다.");
        }
    }
}

Swich라는 객체를 만들어서 예를 들어보자.
ON과 OFF라는 상태를 가지고, 현재 상태를 state라는 변수에 저장한다.
그리고 ON 버튼을 누르는 행위(getOnSwich() 메서드), OFF 버튼을 누르는 행위(getOFFSwich() 메서드)를 통해 스위치의 상태를 바꿀 수 있다.

Client

public class Clinent {
    public static void main(String[] args) {
        Switch test = new Switch();
        test.getONSwich();
        test.getOFFSwitch();
    }
}

행위를 하는 client 객체로 swich를 만들고 상태를 변경할 수 있다.
다음과 같은 결과가 나온다.

스위치가 켜졌습니다. 스위치가 꺼졌습니다.

상태가 추가된다면?

ON과 OFF 외에 WAITING(대기)이라는 상태가 추가된다고 생각해보자.
그렇게 되면 swich 객체의 getOnSwich() 메서드와 getOFFSwich() 메서드에 조건문을 추가해서 현재 state가 WAITING인 경우를 추가해 주어야 한다.
이러한 방식은 조건문을 찾아가며 현재의 상태가 어떻게 변하는지 파악해야 하므로 상태의 변화를 파악하기 어렵고, 새로운 상태가 추가되면 모든 행위 메서드에 새로운 변화의 내용을 추가해 줘야 하기 때문에 유지 보수에 좋지 않다.

스테이트 패턴의 도입

위와 같은 문제 상황을 위해 스테이트 패턴을 도입한다.
스테이트 패턴은 기존의 방식과는 다르게 상태에 관한 인터페이스에 공동으로 수행해야 할 행위 메서드를 지정하고, 상태에게 그 행위 메서드의 실체화를 위임하는 방식이다.

State

public interface State {
    public void getONSwich(Switch testSwitch);
    public void getOFFSwitch(Switch testSwitch);
}

다음과 같은 인터페이스를 만들고 이를 실체화하는 ON과 OFF 메서드를 만들자.

ON

public class ON implements State{
    @Override
    public void getONSwich(Switch testSwitch) {
        System.out.println("이미 ON 상태입니다.");
    }

    @Override
    public void getOFFSwitch(Switch testSwitch) {
        testSwitch.setState(new OFF());
        System.out.println("스위치가 꺼졌습니다.");
    }
}

OFF

public class OFF implements State{
    @Override
    public void getONSwich(Switch testSwitch) {
        testSwitch.setState(new ON());
        System.out.println("스위치가 켜졌습니다.");
    }

    @Override
    public void getOFFSwitch(Switch testSwitch) {
        System.out.println("이미 OFF 상태입니다.");
    }
}

이렇게 상태에 행위 메서드의 구현을 위임했다.
그러면 Switch 객체도 다음과 같이 수정해야 보자.

Switch

public class Switch {

    public void setState(State state) {
        this.state = state;
    }

    private State state;

    public Switch(){
        state = new OFF();
    }

    public void getONSwich(){
        state.getONSwich(this);
    }

    public void getOFFSwitch(){
        state.getOFFSwitch(this);
    }
}

위와 같이 실체화된 상태가 Switch의 객체의 상태를 변경해 주는 set 메서드를 추가하고, 구체적인 행위는 실체화된 상태 객체에 위임한다.

장점

  • 상태변화를 비교적 쉽게 확인할 수 있다.
  • 새로운 상태가 추가되면 메서드에 조건문으로 추가하는 것이 아니라 state를 실체화하는 객체를 만들어주면 된다.
  • 그러므로 상태가 추가된다 해서 Switch의 소스를 수정할 필요가 없다.
  • client는 상태 객체에 대한 지식이 없어도 사용할 수 있다.