어떤 클래스에 여러개의 상태가 있고, 메서드들은 그 상태에 따라서 다른 행동을 하는 클래스가 있다고 하자.
public class GameMachine {
private int COIN_INSERTED = 1;
private int COIN_EMPTY = 2;
private int GAME_PLAYING = 3;
int coins = 0;
int state = COIN_EMPTY;
public void playGame() {
if (state == COIN_INSERTED) {
// play game
} else if (state == COIN_EMPTY) {
// error
} else if (state == GAME_PLAYNG) {
// error
}
}
public void insertCoin() {
if (state == COIN_INSERTED) {
// add coin
} else if (state == COIN_EMPTY) {
// add coin
} else if (state == GAME_PLAYNG) {
// error
}
}
}
이런 식으로 해당 클래스의 모든 메서드에는 그 상태에 대한 분기 처리가 들어가야 한다.
이 방식으로도 코드는 문제없이 돌아가지만, 만약 상태 값이 추가될 경우에는 모든 메서드에 그 상태에 대한 분기문을 다시 작성해주어야 한다.
스테이트 패턴은 이러한 문제를 해결하기 위하여 모든 상태를 클래스로 분리하여 캡슐화 하는 디자인 패턴이다.
위의 예시는 아래와 같이 클래스를 분리할 수 있겠다.
classDiagram
State <|-- EmptyCoinState
State <|-- CoinInsertedState
State <|-- PlayingGameState
class EmptyCoinState{
playGame()
insertCoin()
}
class CoinInsertedState{
playGame()
insertCoin()
}
class PlayingGameState{
playGame()
insertCoin()
}
class State{
<<interface>>
playGame()
insertCoin()
}
EmptyCoinState 클래스를 구현해보면 다음과 같다.
public class EmptyCoinState implements State {
// 메인 클래스를 인스턴스 변수로 선언
private GameMachine gameMachine;
public EmptyCoinState(GameMachine gameMachine) {
this.gameMachine = gameMachine;
}
public void playGame() {
// Coin이 없는 상태로 게임을 실행하면 에러가 발생
}
public void insertCoin() {
// add coin
// gameMachie 객체의 상태를 현재 클래스의 인스턴스로 변경
gameMachine.setState(gameMachine.getEmptyCoinState());
}
}
public class GameMachine {
// 각 State 클래스의 객체를 인스턴스 변수로 선언
private EmptyCoinState emptyCoinState;
private CoinInsertedState coinInsertedState;
private PlayingGameState playingGameState;
// 상태를 담을 변수 선언
private State state;
public GameMachine() {
this.emptyCoinState = new EmptyCoinState(this)
this.coinInsertedState = new CoinInsertedState(this)
this.playingGameState = new PlayingGameState(this)
}
public void playGame() {
// 현재 상태의 메서드를 호출
state.playGame();
}
public void insertCoin() {
state.insertCoin();
}
public void setState(State state) {
this.state = state;
}
// State 객체들의 getter...
}
이렇게 남은 State 클래스들도 구현을 마치게 되면, 이전 구조와 비교하여 다음과 같은 이점을 얻게된다.
State 패턴을 사용하면 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻는다.
다이어그램 상으로는 비슷해 보일 수 있으나, 실제 용법 면에서 차이가 있다.