Tech & Programming/Pattern & Design

스테이트 패턴(State Pattern) 이란?

소스코드 요리사 2019. 1. 11. 17:55

이번 시간에는 스테이트 패턴(State Patter)에 대해서 알아보겠습니다.



사탕 뽑기 기계 프로그램을 구현한다고 가정하면 사탕뽑기 기계는 '동전없음', '동전있음', '상품배출', '상품품절' 상태를 가질 것입니다.

그리고, 사탕뽑기의 각 상태는 아래와 같은 행위를 통해서 다른 상태로 변이하게 됩니다.



이 행위들과 상태를 코드로 구현하면 아래와 같이 구현할 수 있을껍니다.


public class GumBallMachine {

    final static int SOLD_OUT = 0;
    final static int NO_QUARTER = 1;
    final static int HAS_QUARTER = 2;
    final static int SOLD = 3;

    private int numberOfGumBall = 0;
    public int state = SOLD_OUT;

    public GumBallMachine(int numberOfGum) {
        if(numberOfGum > 0) {
            state = NO_QUARTER;
        }
    }

    public int getNumberOfGumBall() {
        return numberOfGumBall;
    }


    public void insertQuarter() {
		// 동전삽입 시 동작 구현
    }

    public void returnQuarter() {
		// 동전반환 시 동작 구현
    }

    public void turnCrank() {
		// 뽑기 손잡이를 돌릴 때 동작 구현
    }

    public void dispense() {
		// 상품 배출 시 동작 구현
    }

}


그리고, 행위를 수행했을 때 상태에 따라 벌어지는 일들이 다르기 때문에 상태를 비교하여 분기하는 분기코드가 반복되어 들어가게 됩니다.

쉽게 예를 들어 설명하면 '동전없음' 상태 일 때 insertQuarter()의 실행결과와 '동전있음' 일 때의 실행결과가 다르다는 이야기 입니다.

'동전없음' 일 경우는 동전이 정상적으로 삽입되고, '동전있음' 상태로 변경되면 되겠고, '동전있음' 상태라면 동전이 있다고 경고 메시지를 띄워주는 식의 구현을 해야겠지요.


따라서, 아래와 같은 분기코드가 반복됩니다.


if(상태 == 동전있음) {


} else if(상태 == 동전없음) {


} else if(상태 == 상품배출) {


} else if(상태 == 상품품절) {


}


이런 반복문을 각 상태에 따른 클래스에 캡슐화 하여 각자 자기의 역할을 구현하게 하는 것이 바로 스테이트(State) 패턴입니다.

스테이트 패턴의 다이어그램은 아래와 같습니다.


즉, 아래와 같이 아래와 같이 각 행위들을 인터페이스로 작성한 뒤에 각 상태 클래스에서 구체화 시킵니다.


public interface State {



    public void insertQuarter();

    public void returnQuarter();

    public void turnCrank() ;

    public void dispense() ;



}



public class SoldState implements State {



    private GumBallMachine gumBallMachine;



    public SoldState(GumBallMachine gumBallMachine) {

        this.gumBallMachine = gumBallMachine;

    }



    @Override

    public void insertQuarter() {

        System.out.print("잠깐만 기다려주세요. 상품이 나오고 있습니다.");

    }



    @Override

    public void returnQuarter() {

        System.out.print("삽입된 동전이 없습니다.");

    }



    @Override

    public void turnCrank() {

        System.out.print("상품이 나오고 있습니다. 손잡이는 이미 돌리셨습니다.");

    }



    @Override

    public void dispense() {

        gumBallMachine.releaseGumBall();

        System.out.print("상품을 꺼냈습니다.");



        if(gumBallMachine.getNumberOfGumBall() > 0 ) {

            gumBallMachine.setState(gumBallMachine.getNoQuarterState());

        }

        else {

            System.out.print("상품이 모두 팔렸습니다.");

            gumBallMachine.setState(gumBallMachine.getSoldOutState());

        }

    }

}


이렇게 하면 상태가 추가, 수정되더라도 수정이 용이한 코드로 탈바꿈하게 됩니다.


정리해서 이야기 하면, 스테이트 패턴은 객체의 내부상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있습니다. 

마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다




스트래티지 패턴  VS 스테이트 패턴

일반적으로 스트래티지 패턴은 서브클래스를 만드는 방법을 대신하여 유연성을 극대화하기 위한 용도로 쓰입니다.

상속을 이용해서 클래스의 행동을 정의하다 보면 행동을 변경해야할 때 마음대로 변경하기가 힘들죠.

하지만 스트래티지 패턴을 사용하면 구성을 통해 행동을 정의하는 객체를 유연하게 변경할 수 있습니다.


스테이트 패턴은 컨텍스트 객체에 수많은 조건문을 집어 넣는 대신에 사용할 수 있는 패턴이라고 보면 됩니다.

행동을 상태 객체 내에 캡슐화 시키면 컨텍스트 내의 상태 객체를 바꾸는 것만으로도 컨텍스트 객체의 행동을 바꿀 수 있습니다.



전체 소스 : https://github.com/JunpilPark/DesignPatternStudy/tree/master/statePattern/src/main/java/com/example/statepattern