스테이트 패턴
은 객체 내부의 상태에 따라 동작을 변환할 때 이용하는 패턴입니다. 스테이트 패턴
을 이용하면, 객체에 대해 여러 동작을 정의할 때 스테이트 객체만 수정, 추가하면 됩니다. 또한 스테이트 패턴
을 사용하지 않을경우 조건문 등을 이용해서 동작을 제어해야하는데 스테이트 객체를 이용함으로써 클래스 코드가 간단해집니다. 단, 상태에 따라서 스테이트 객체가 증가하기 때문에, 동작이 많아질 경우 스테이트 객체가 늘어난다는 단점도 갖고 있습니다. 스테이트 패턴
은 행동 패턴에 속합니다.
오락실이나 뽑기 기계 근처에 가면 항상 동전 교환기계가 있습니다. 이 기계는 스테이트 패턴으로 만들어보기 아주 적합한데요. 기계 내부에 동전이 적당하다면 정상적인 작동을 할 것이고, 동전이 없다면, 기계가 작동을 못하도록 해야겠죠.
상태 객체를 만들기 전에 상태 인터페이스 부터 정의합니다. 기계 내부에 동전이 있던 없던 다음 동작들은 무조건 실행하므로 인터페이스로 정의해줍니다. 기계에 지폐 삽입하기, 기계에서 지폐 반환하기, 동전 반환하기. 이 세가지 동작은 어떤 상태이든지 실행은 됩니다. 상태에 따라서 세부적인 내용이 달라질 뿐이죠.
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상태로 총 세가지 입니다. 당장 구현은 세 가지로 할 것이지만, 추가 기능을 추후에 만들게 되면 상태를 하나 더 만들면 됩니다.
먼저 기계에 돈이 삽입되지 않은 NoCash 상태의 클래스입니다.
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("지폐가 삽입되지 않아 반환 할 동전이 없습니다.");
}
}
기계에 돈을 삽입한 HasCash 상태입니다.
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());
}
}
}
마지막으로 기계에 돈이 없는 상태인 NoMoreCoin 상태입니다.
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("기계 내에 동전이 없습니다.");
}
}
각 상태마다 같은 기능을 가졌지만 동작이 다른것을 확인할 수 있습니다.
지금까지 상태에 따라서 동작을 변환하고 싶을 때 이용하는 패턴인 스테이트 패턴이었습니다.