전략 패턴과 상태 패턴은 용도가 다를 뿐 그 설계는 비슷하다.
상태 패턴은 내부 상태를 바꿔 객체가 행동을 바꿀 수 있도록 한다.
public class StateTest {
static 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;
//현재 상태 변수
int state = SOLD_OUT;
int count = 0 ;
public GumballMachine(int count) {
this.count= count;
if(count>0) {
state = NO_QUARTER;
}
}
public void insertQuarter() {
if(state == HAS_QUARTER) {
System.out.println("동전은 한 개만 넣어 주세요.");
}else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("동전을 넣으셨습니다.");
}else if (state == SOLD_OUT) {
System.out.println("매진되었습니다. 다음 기회에 이용해 주세요.");
}else if (state == SOLD) {
System.out.println("알맹이를 내보내고 있습니다.");
}
}
public void ejectQuarter() {
if(state == HAS_QUARTER) {
System.out.println("동전이 반환됩니다.");
state = NO_QUARTER;
}else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("동전을 넣어 주세요");
}else if (state == SOLD_OUT) {
System.out.println("동전을 넣지 않으셨습니다. 동전이 반환되지 않습니다.");
}else if (state == SOLD) {
System.out.println("이미 알맹이를 뽑으셨습니다.");
}
}
public void turnCrank() {
if(state == SOLD) {
System.out.println("손잡이는 한 번만 돌려 주세요.");
}else if (state == NO_QUARTER) {
System.out.println("동전을 넣어 주세요.");
}else if (state == SOLD_OUT) {
System.out.println("매진되었습니다.");
}else if (state == HAS_QUARTER) {
System.out.println("손잡이를 돌리셨습니다.");
state = SOLD;
dispense();
}
}
public void dispense() {
if(state == SOLD) {
System.out.println("알맹이를 내보내고 있습니다.");
if(0 == (count -= 1)) {
System.out.println("더 이상 알맹이가 없습니다.");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
}else if (state == NO_QUARTER) {
System.out.println("동전을 넣어 주세요.");
}else if (state == SOLD_OUT) {
System.out.println("매진입니다.");
}else if (state == HAS_QUARTER) {
System.out.println("알맹이를 내보낼 수 없습니다.");
}
}
public void refill(int numGumBalls) {
this.count = numGumBalls;
state = NO_QUARTER;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004\n");
result.append("Inventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n갬블머신 현재 상태 ");
if (state == SOLD_OUT) {
result.append("매진");
} else if (state == NO_QUARTER) {
result.append("동전 투입 대기 중");
} else if (state == HAS_QUARTER) {
result.append("손잡이 돌리기 대기 중");
} else if (state == SOLD) {
result.append("알맹이 배출 중");
}
result.append("\n");
return result.toString();
}
}
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.ejectQuarter();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
갬블머신 현재 상태 동전 투입 대기 중
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
갬블머신 현재 상태 동전 투입 대기 중
동전을 넣으셨습니다.
동전이 반환됩니다.
동전을 넣어 주세요.
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
갬블머신 현재 상태 동전 투입 대기 중
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
동전을 넣어 주세요
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
갬블머신 현재 상태 손잡이 돌리기 대기 중
동전은 한 개만 넣어 주세요.
동전은 한 개만 넣어 주세요.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
더 이상 알맹이가 없습니다.
매진되었습니다. 다음 기회에 이용해 주세요.
매진되었습니다.
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
갬블머신 현재 상태 매진
각 행동마다 상태를 전이하는 부분이 있다.
그리고 각 행동(메서드) 마다 상태별 경우의 수에 대한 코드가 있다.
public class StatePattern {
static interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
void refill();
}
static abstract class BaseState implements State{
GumballMachine gumballMachine;
public BaseState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
}
static class NoQuarterState extends BaseState{
public NoQuarterState(GumballMachine gumballMachine) {
super(gumballMachine);
}
public void insertQuarter() {
System.out.println("동전을 넣으셨습니다.");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println("동전을 넣어 주세요");
}
public void turnCrank() {
System.out.println("동전을 넣어 주세요.");
}
public void dispense() {
System.out.println("동전을 넣어 주세요.");
}
public void refill() {
}
@Override
public String toString() {
return "동전 기다리는 중";
}
}
static class HasQuarterState extends BaseState{
public HasQuarterState(GumballMachine gumballMachine) {
super(gumballMachine);
}
public void insertQuarter() {
System.out.println("동전은 한 개만 넣어 주세요.");
}
public void ejectQuarter() {
System.out.println("동전이 반환됩니다.");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("손잡이를 돌리셨습니다.");
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println("알맹이를 내보낼 수 없습니다.");
}
public void refill() {
}
@Override
public String toString() {
return "손잡이 돌기는 것 기다리는 중";
}
}
static class SoldOutState extends BaseState{
public SoldOutState(GumballMachine gumballMachine) {
super(gumballMachine);
}
public void insertQuarter() {
System.out.println("매진되었습니다. 다음 기회에 이용해 주세요.");
}
public void ejectQuarter() {
System.out.println("매진되었습니다.");
}
public void turnCrank() {
System.out.println("손잡이를 돌렸지만, 알맹이는 없습니다.");
}
public void dispense() {
System.out.println("알맹이를 내보낼 수 없습니다.");
}
public void refill() {
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public String toString() {
return "매진";
}
}
static class SoldState extends BaseState{
public SoldState(GumballMachine gumballMachine) {
super(gumballMachine);
}
public void insertQuarter() {
System.out.println("알맹이를 내보내고 있습니다.");
}
public void ejectQuarter() {
System.out.println("이미 알맹이를 뽑으셨습니다.");
}
public void turnCrank() {
System.out.println("손잡이는 한 번만 돌려 주세요.");
}
public void dispense() {
}
public void refill() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("이런, 알맹이가 다 떨어졌어요");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
@Override
public String toString() {
return "알맹이 내보내는 중";
}
}
static class GumballMachine{
State state;
int count = 0;
final State soldOutState;
final State noQuarterState;
final State hasQuarterState;
final State soldState;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
state = numberGumballs > 0 ? noQuarterState : soldOutState;
}
void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
public void refill(int count) {
this.count += count;
System.out.println("겜볼 기계가 방금 리필되었습니다. 개수는 다음과 같습니다." + this.count);
state.refill();
}
public void releaseBall() {
System.out.println("알맹이가 슬롯에서 굴러 나옵니다....");
if (count > 0) {
count = count - 1;
}
}
public int getCount() {
return count;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\n겜블 머신_");
result.append("\n자바 기반 Gumball Model #2004");
result.append("\n항목: " + count + " 알맹이");
if (count != 1) {
result.append("들");
}
result.append("\n");
result.append("머신 상태 :" + state + "\n");
return result.toString();
}
}
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(2);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.refill(5);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
상태 인터페이스로 추상화 다형성을 이용
각 상태의 행동을 별개의 클래스로 국지화
앞으로 생길 수 있는 새로운 상태에 대해 OCP를 준수할 수 있다.
상태 패턴의 정의
객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다.
마치 객체의 클래스가 바뀌는 것과 같은 결과
상태 패턴과 전략 패턴은 비슷하나 용도가 다르다.
상태 패턴은 상태 객체에 일련 행동이 캡슐화, Context 객체에서 여러 상태 객체 중 한 객체에게 모든 행동을 위임한다.
클라이언트는 상태 객체를 몰라도 된다.
전략 패턴은 클라이언트가 Context에게 어떤 전략 객체를 사용할지를 지정한다. 주로 런타임 환경에서 객체를 변경할 수 있는 유연성을 제공하는 용도로 쓰인다.
상태 패턴은 객체에 수많은 조건문을 넣는 대신 상태 패턴을 사용한다고 생각하면 된다.
상태 패턴에서 상태 전환을 Context에서 해도, 상태 클래스에서 해도 상관없다. 다만, 상태 전환 코드를 상태 클래스에 넣으면 상태 클래스 간 의존성이 생긴다. 이 문제로 위 예제에선 겜블머신에 게터 메소드를 사용했다.
'IT책, 강의 > 헤드 퍼스트 디자인 패턴' 카테고리의 다른 글
CHAPTER 11 객체 접근 제어하기 프록시 패턴 (0) | 2023.06.03 |
---|---|
CHAPTER 09 컬렉션 잘 관리하기 반복자 패턴과 컴포지트 패턴 (0) | 2023.05.30 |
CHAPTER 08 알고리즘 캡슐화하기 템플릿 메소드 패턴 (0) | 2023.05.28 |
CHAPTER 07 적응시키기 어댑터 패턴과 퍼사드 패턴 (0) | 2023.05.26 |
CHAPTER 06 호출 캡슐화하기 커맨드 패턴 (0) | 2023.05.24 |