반응형
[헤드퍼스트 디자인패턴] 09. 상태 패턴(State Pattern)
1. 상태 패턴이란?
객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.
2. 상태 기계 기초 지식 알아보기
뽑기 기계를 만들때, 상태를 이용해 기계를 구현하는 방법을 간단하게 살펴보자.
2.1 우선 상태들을 모아본다.
총 4개의 상태가 존재한다.
(No Quarter : 동전 없음, Has Quarter : 동전 있음, Gumball Sold : 알맹이 판매, Out of Gumballs : 알맹이 매진)
2.2 현재 상태를 저장하는 인스턴스 변수를 만들고 각 상태의 값을 정의한다.
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
2.3 이 시스템에서 일어날 수 있는 모든 행동을 모아 본다.
- 동전 투입
- 동전 반환
- 손잡이 돌림
- 알맹이 내보냄
2.4 뽑기 기계 코드를 구현해본다.
public 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("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
System.out.println("Please wait, we're already giving you a gumball");
}
}
public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}
public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}
private void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}
public void refill(int numGumBalls) {
this.count = numGumBalls;
state = NO_QUARTER;
}
public String toString() {
// 구현
}
}
-> 위와 같이 구현하면, 새로운 추가 요청 사항이 생겼을 때 모든 메서드에 조건문을 전부 추가해줘야한다.
-> 확장의 어려움
3. 새로운 디자인 구성
- 뽑기 기계와 관련된 모든 행동에 관한 메소드가 들어있는 State 인터페이스를 정의
- 기계의 모든 상태를 대상으로 상태 클래스를 구현
- 조건문 코드를 전부 없애고 상태 클래스에 모든 작업을 위임
4. State 클래스 구현하기
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = 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("동전을 넣어주세요");
}
}
-> 상황에 따라 현재 상태가 다른 상태로 변경될 수 있다.
5. 뽑기 기계 전체 코드
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state;
int count = 0;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
} else {
state = soldOutState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
// 기타 메소드..
}
6. 구현을 구조적으로 바꿈으로써 얻은 결과
- 각 상태의 행동을 별개의 클래스로 국지화했다.
- 관리하기 힘든 골칫덩어리 if문 제거
- 각 상태를 변경에는 닫혀있게 했고 확장에는 열려있게 수정함(OCP)
- 더 이해하기 좋은 코드 베이스와 클래스 구조
728x90
반응형
'스터디 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[헤드퍼스트 디자인패턴] 10. 프록시 패턴 (Proxy pattern) (0) | 2022.08.21 |
---|---|
[헤드퍼스트 디자인패턴] 08. 반복자 패턴 (Iterator Pattern) 과 컴포지트 패턴 (Composite Pattern) (0) | 2022.07.31 |
[헤드퍼스트 디자인패턴] 08. 템플릿 메소드 패턴(Template method) (0) | 2022.07.24 |
[헤드퍼스트 디자인패턴] 07. 어댑터 패턴(Adapter Pattern)과 퍼사드 패턴(Facade Pattern) (0) | 2022.07.17 |
[헤드퍼스트 디자인패턴] 06. 커맨드 패턴(Command Pattern) (0) | 2022.07.10 |
댓글