본문 바로가기
Programming/디자인 패턴

스테이트 패턴 State Pattern

by Bam_t 2021. 9. 29.
728x90

State는 상태라는 의미를 가지고 많이 쓰이는 단어입니다. 이처럼 상태에 따라 객체를 변환할 때 이용하는 패턴이 스테이트 패턴입니다.


1. 스테이트 패턴

스테이트 패턴은 객체 내부의 상태에 따라 동작을 변환할 때 이용하는 패턴입니다. 스테이트 패턴을 이용하면, 객체에 대해 여러 동작을 정의할 때 스테이트 객체만 수정, 추가하면 됩니다. 또한 스테이트 패턴을 사용하지 않을경우 조건문 등을 이용해서 동작을 제어해야하는데 스테이트 객체를 이용함으로써 클래스 코드가 간단해집니다. 단, 상태에 따라서 스테이트 객체가 증가하기 때문에, 동작이 많아질 경우 스테이트 객체가 늘어난다는 단점도 갖고 있습니다.

 

 

2. 스테이트 패턴 구현하기

오락실이나 뽑기 기계 근처에 가면 항상 동전 교환기계가 있습니다. 이 기계는 스테이트 패턴으로 만들어보기 아주 적합한데요. 기계 내부에 동전이 적당하다면 정상적인 작동을 할 것이고, 동전이 없다면, 기계가 작동을 못하도록 해야겠죠.

 

상태 객체를 만들기 전에 상태 인터페이스 부터 정의합니다. 기계 내부에 동전이 있던 없던 다음 동작들은 무조건 실행하므로 인터페이스로 정의해줍니다. 기계에 지폐 삽입하기, 기계에서 지폐 반환하기, 동전 반환하기. 이 세가지 동작은 어떤 상태이든지 실행은 됩니다. 상태에 따라서 세부적인 내용이 달라질 뿐이죠.

public interface State {
    public void insertCash(CoinChanger coinChanger);
    public void ejectCash(CoinChanger coinChanger);
    public void dispenseCoin(CoinChanger coinChanger, int cash);
}

 

이제 상태 객체를 만들기 전에 CoinChanger 객체 부터 만들고 넘어가려고 합니다. 코드가 길어서 전체 코드부터 소개한 뒤에 몇 부분으로 나누어서 설명드리려고 합니다.

public class CoinChanger {
    private State noCashState;
    private State hasCashState;
    private State noMoreCoinState;
    private State currentState;
    private int coin = 100;

    public CoinChanger() {
        noCashState = new NoCash();
        hasCashState = new HasCashState();
        noMoreCoinState = new NoMoreCoin();
        currentState = noCashState;
    }

    public void insertCash() {
        currentState.insertCash(this);
    }

    public void ejectCash() {
        currentState.ejectCash(this);
    }

    public void dispenseCoin(int cash) {
        currentState.dispenseCoin(this, cash);
    }

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

    public int getCurrentCoin() {
        return coin;
    }

    public void setCurrentCoin(int coin) {
        this.coin = coin;
    }

    public State getNoCashState() {
        return noCashState;
    }

    public State getHasCashState() {
        return hasCashState;
    }

    public State getNoMoreCoinState() {
        return noMoreCoinState;
    }

    public State getCurrentState() {
        return currentState;
    }
}

 

우선 동작 메소드들입니다. 돈을 삽입하고, 반환하고 동전을 배출하는 세가지 동작인데요. currentState라는 변수에 따라서 다른 클래스의 메소드 동작들이 호출됩니다. 즉, 현재 상태에 따라서 같은 이름의 메소드 동작이지만, 동작이 달라지는 것이죠. 

public void insertCash() {
        currentState.insertCash(this);
    }

    public void ejectCash() {
        currentState.ejectCash(this);
    }

    public void dispenseCoin(int cash) {
        currentState.dispenseCoin(this, cash);
    }

 

이하의 부분들은 각 상태 객체들을 가져오기 위한 부분입니다. 

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

    public int getCurrentCoin() {	//현재 동전 갯수 반환
        return coin;
    }

    public void setCurrentCoin(int coin) {
    //동전 갯수 설정, 동전을 추가하거나, 동전을 반환해서 없어진 동전 갯수 설정에 사용
        this.coin = coin;
    }

    //이하는 상태를 가져오기 위한 메소드들
    public State getNoCashState() {
        return noCashState;
    }

    public State getHasCashState() {
        return hasCashState;
    }

    public State getNoMoreCoinState() {
        return noMoreCoinState;
    }

    public State getCurrentState() {
        return currentState;
    }

 

이제 상태 클래스들을 만들건데, 다음과 같은 상태가 있습니다. 기계에 지폐를 넣지 않은 NoCash 상태, 기계에 지폐가 삽입된 HasCash상태, 기계 내부에 동전이 없는 NoMoreCoins상태로 총 세가지 입니다. 당장 구현은 세 가지로 할 것이지만, 추가 기능을 추후에 만들게 되면 상태를 하나 더 만들면 됩니다.

public class NoCash implements State {
    @Override
    public void insertCash(CoinChanger coinChanger) {
        System.out.println("지폐 삽입. 상태 변경");

        coinChanger.setState(coinChanger.getHasCashState());
    }

    @Override
    public void ejectCash(CoinChanger coinChanger) {
        System.out.println("지폐가 삽입된 상태가 아닙니다.");
    }

    @Override
    public void dispenseCoin(CoinChanger coinChanger, int cash) {
        System.out.println("지폐가 삽입되지 않아 반환 할 동전이 없습니다.");
    }
}
public class HasCashState implements State {
    @Override
    public void insertCash(CoinChanger coinChanger) {
        System.out.println("이미 지폐가 투입되어 있습니다.");
    }

    @Override
    public void ejectCash(CoinChanger coinChanger) {
        System.out.println("삽입된 지폐를 반환합니다. 상태 변경");

        coinChanger.setState(coinChanger.getNoCashState());
    }

    @Override
    public void dispenseCoin(CoinChanger coinChanger, int cash) {
        int currentCoin = coinChanger.getCurrentCoin();

        if(currentCoin < cash) {
            System.out.println("동전교환기 내의 동전이 충분하지 않습니다.");
        }
        else {
            System.out.println(cash+"개의 동전 배출");

            int remainCoin = currentCoin - cash;

            coinChanger.setCurrentCoin(remainCoin);
        }

        currentCoin = coinChanger.getCurrentCoin();

        if(currentCoin <= 0) {
            coinChanger.setState(coinChanger.getNoMoreCoinState());
        }
    }
}
public class NoMoreCoin implements State {
    @Override
    public void insertCash(CoinChanger coinChanger) {
        System.out.println("이미 지폐가 삽입되어있습니다. +기계 내 동전 부족+");
    }

    @Override
    public void ejectCash(CoinChanger coinChanger) {
        System.out.println("지폐를 반환합니다. +기계 내 동전 부족+");

        coinChanger.setState(coinChanger.getNoCashState());
    }

    @Override
    public void dispenseCoin(CoinChanger coinChanger, int cash) {
        System.out.println("기계 내에 동전이 없습니다.");
    }
}

각 상태마다 같은 기능을 가졌지만 동작이 다른것을 확인할 수 있습니다.

지금까지 상태에 따라서 동작을 변환하고 싶을 때 이용하는 패턴인 스테이트 패턴이었습니다.


https://github.com/Bam-j/DesignPattern/tree/master/src/State

 

GitHub - Bam-j/DesignPattern: 디자인 패턴 블로그 기재

디자인 패턴 블로그 기재. Contribute to Bam-j/DesignPattern development by creating an account on GitHub.

github.com

 

728x90

댓글