State Pattern
State Pattern(스테이트 패턴)
클래스다이어그램
http://en.wikipedia.org/wiki/State_pattern
클래스 역할
-
Context
- 클라이언트의 인터페이스(API)를 가지며, 클라이언트의 요청을 ConcreteState 객체에게 위임합니다.
-
State
- State 인터페이스(API)는 상태에 의존한 동작을 하는 메소드의 집합입니다.
-
ConcreteState
- State 인터페이스(API)를 구현해 구체적인 각각의 상태를 나타냅니다.
패턴 설명
State 패턴에서는 클래스를 하나의 '상태'로 표현합니다. 상태라는건 추상적이기 때문에 클래스로 표현한다는게 어색할 수도 있지만 상태를 클래스로 표현하면 클래스의 교체를 통해 '상태의 변화'를 나타내고 다른 상태가 필요할 때 추가할 수도 있습니다.
State 패턴은 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있습니다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다. State 패턴은 각각의 상태를 캡슐화한 다음 현재 상태를 나타내는 객체에게 행동을 위임(delegation)하기 때문에, 내부 상태가 바뀜에 따라서 행동이 달라지게 됩니다.
클라이언트 입장에서 보면 Context 객체만을 사용하는데 행동이 완전히 달라지기 때문에 다른 객체를 사용하는 것 같은 착각을 할 수 있습니다. 실제로는 State 인터페이스(API)를 구현한 여러 ConcreteState객체를 구성(composition)을 통해 바꿔가며 사용하는 거지만 말이죠.
주의 사항
예제 프로그램에서 Context 객체의 상태를 변환하는 메소드는 Context가 가지고 있지만 그 사용은 각각의 ConcreteState 객체에서 합니다. 이 방법은 장/단점을 가지고 있습니다.
장점은 '언제 다른 상태로 전환하는가'에 대한 답이 해당 ConcreteState 객체에 정리되 있다는 점이고, 단점은 ConcreteState 객체가 상태를 변환할 다른 ConcreteState 객체를 알아야 한다는 점입니다. 이것은 의존 관계를 만들기 때문에 유지/보수에 좋지 않죠.
다른 방법으로, Context 객체가 직접 자신의 상태를 변화시키는 방법이 있는데 이 방법의 장점은 각각의 ConcreteState 객체가 OCP를 준수하게 됩니다. 단점은 Context 객체가 모든 ConcreteState 객체를 의존하게 됩니다.
소스 코드 // 헤드 퍼스트
예제 설명
뽑기 기계를 구현한 프로그램입니다.
State(상태 인터페이스)
- public interface State {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
ConcreteState(상태 구현 클래스)
- public class HasQuarterState implements State {
Random randomWinner = new Random(System.currentTimeMillis());
GumballMachine machine;
public HasQuarterState(GumballMachine machine) {
this.machine = machine;
}
@Override
public void dispense() {
System.out.println("알맹이가 나갈 수 없습니다!");
}
@Override
public void ejectQuarter() {
System.out.println("동전을 꺼냅니다.");
machine.setState(machine.getHasQuarterState());
}
@Override
public void insertQuarter() {
System.out.println("동전은 한 개만 넣어주세요.");
}
@Override
public void turnCrank() {
System.out.println("손잡이를 돌렸습니다.");
int winner = randomWinner.nextInt(10);
if ((winner == 0) && (machine.getCount() > 1)) {
machine.setState(machine.getWinnerState());
} else {
machine.setState(machine.getSoldState());
}
}
} - public class NoQuarterState implements State {
GumballMachine machine;
public NoQuarterState(GumballMachine machine) {
this.machine = machine;
}
@Override
public void dispense() {
System.out.println("동전을 넣어주세요.");
}
@Override
public void ejectQuarter() {
System.out.println("동전을 넣어주세요");
}
@Override
public void insertQuarter() {
System.out.println("동전을 넣었습니다.");
machine.setState(machine.getHasQuarterState());
}
@Override
public void turnCrank() {
System.out.println("동전을 넣어주세요.");
}
} - public class SoldOutState implements State {
GumballMachine machine;
public SoldOutState(GumballMachine machine) {
this.machine = machine;
}
@Override
public void dispense() {
System.out.println("꺼낼 알맹이가 없습니다.");
}
@Override
public void ejectQuarter() {
System.out.println("들어있는 동전이 없습니다.");
}
@Override
public void insertQuarter() {
System.out.println("동전을 넣을 수 없습니다. 매진입니다.");
}
@Override
public void turnCrank() {
System.out.println("매진입니다.");
}
} - public class SoldState implements State {
GumballMachine machine;
public SoldState(GumballMachine machine) {
this.machine = machine;
}
@Override
public void dispense() {
machine.releaseBall();
if (machine.getCount() > 0) {
machine.setState(machine.getNoQuarterState());
} else {
machine.setState(machine.getSoldOutState());
}
}
@Override
public void ejectQuarter() {
System.out.println("이미 알맹이를 뽑았습니다.");
}
@Override
public void insertQuarter() {
System.out.println("잠깐만 기다려 주세요. 알맹이가 나가고 있습니다.");
}
@Override
public void turnCrank() {
System.out.println("손잡이는 한 번만 돌려주세요.");
}
} - public class WinnerState implements State {
GumballMachine machine;
public WinnerState(GumballMachine machine) {
this.machine = machine;
}
@Override
public void dispense() {
System.out.println("축하합니다! 알맹이를 하나 더 받을 수 있습니다!");
machine.releaseBall();
if (machine.getCount() == 0) {
System.out.println("더 이상 알맹이가 없습니다.");
machine.setState(machine.getSoldOutState());
} else {
machine.releaseBall();
if (machine.getCount() > 0) {
machine.setState(machine.getNoQuarterState());
} else {
System.out.println("더 이상 알맹이가 없습니다.");
machine.setState(machine.getSoldOutState());
}
}
}
@Override
public void ejectQuarter() {
System.out.println("이미 알맹이를 뽑았습니다.");
}
@Override
public void insertQuarter() {
System.out.println("잠깐만 기다려 주세요. 알맹이가 나가고 있습니다.");
}
@Override
public void turnCrank() {
System.out.println("손잡이는 한 번만 돌려주세요.");
}
}
Context(상태를 가지는 환경 클래스)
- public class GumballMachine {
private State soldOutState;
private State soldState;
private State noQuarterState;
private State hasQuarterState;
private State winnerState;
private State state = soldOutState;
private int count = 0;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
soldState = new SoldState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
winnerState = new WinnerState(this);
this.count = numberGumballs;
if (count > 0) {
state = noQuarterState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("알맹이가 슬롯에서 굴러 내려옵니다.");
if (count != 0) {
count = count - 1;
}
}
public String toString() {
StringBuffer state = new StringBuffer("\n주식회사 왕뽑기\n");
state.append("자바로 돌아가는 2009년형 뽑기 기계\n");
state.append("남은 개수: " + count + "개\n");
if (count != 0) {
state.append("동전 투입 대기중\n");
} else {
state.append("매진\n");
}
return state.toString();
}
public State getSoldOutState() {
return soldOutState;
}
public State getSoldState() {
return soldState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getWinnerState() {
return winnerState;
}
public State getState() {
return state;
}
public int getCount() {
return count;
}
public void refill(int count) {
this.count = count;
if (getState() == soldOutState) {
setState(noQuarterState);
}
}
}
Client(클라이언트)
- public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine machine = new GumballMachine(5);
System.out.println(machine);
machine.insertQuarter();
machine.turnCrank();
System.out.println(machine);
machine.insertQuarter();
machine.turnCrank();
machine.insertQuarter();
machine.turnCrank();
System.out.println(machine);
machine.insertQuarter();
machine.turnCrank();
machine.insertQuarter();
machine.turnCrank();
System.out.println(machine);
machine.refill(10);
System.out.println(machine);
}
}
Comments (0)