티스토리 뷰
상태 패턴
상태 패턴은 객체의 내부 상태가 바뀔 때마다 객체의 행동을 바꿀 수 있도록 도와주는 패턴입니다. 마치 객체의 클래스가 바뀌는 것처럼 보여줄 수 있습니다.
- 객체의 상태를 별도 클래스로 캡슐화하여 현재 상태를 나타내는 객체에게 행동을 위임합니다.
- 즉 내부 상태가 바뀔 때마다 행동이 달라진다는 사실을 쉽게 알 수 있습니다.
구조
- Document(=Context): 모든 행동을 자체적으로 구현하는 대신, 현재 상태를 나타내는 상태 객체 하나를 참조해 그 상태 객체에게 행동들을 위임합니다.
- State: 모든 구상 상태 클래스들이 구현해야 하는 공통 인터페이스입니다.
- Draft(=ConcreateState): Document로부터 전달된 요청을 상태 별로 구현하여 처리합니다.
예제
책에서는 동전을 넣어 상품을 뽑는 뽑기 기계를 구현해보고 있는데요.
뽑기 기계 회사로부터 전달받은 기계의 상태 다이어그램은 다음과 같습니다.
1. 4개의 상태를 캡슐화할 State 인터페이스 만들기
public interface State {
// 동전이 들어올 때 해야 할 일
public void insertQuarter();
// 동전을 반환할 때 해야 할 일
public void ejectQuarter();
// 손잡이가 돌아갔을 때 해야 할 일
public void turnCrank();
// 알맹이를 내보낼 때 해야 할 일
public void dispense();
}
2. 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("동전을 넣어주세요.");
}
}
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = 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 class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("알맹이를 내보내고 있습니다.");
}
public void ejectQuarter() {
System.out.println("이미 알맹이를 뽑으셨습니다.");
}
public void turnCrank() {
System.out.println("손잡이는 한 번만 돌려주세요.");
}
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = 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("내보낼 알맹이가 없습니다.");
}
}
3. 뽑기 기계 구현하기
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;
}
void releaseBall() {
System.out.println("알맹이를 내보내는 중...");
if (count > 0) {
count -= 1;
}
}
...
}
정상성 점검(Sanity Check)
GumballMachine을 최종적으로 납품하기 전에 마지막으로 몇 가지를 체크해봅시다.
1. dispense() 메소드가 항상 호출됩니다.
동전 없이 손잡이를 돌려도 dispense()가 호출되고 있습니다.
turnCrank()에서 boolean 값을 리턴하게 해서 문제를 해결할 수 있을 것 같습니다.
2. 상태 전환 정보가 모두 상태 클래스에 있습니다.
상태 전환에 대한 책임은 모두 상태 클래스에게 있습니다.
상태들의 수가 매우 많아 뽑기 기계가 관리하기 힘들 때에는 이 방법이 유리할 수 있습니다.
그러나 상태 전환에 대한 책임이 구상 클래스에 있을 때, 프로그래머의 유지 보수 과정이 힘들어집니다.
뽑기 기계의 전반적인 동작 과정을 알기 위해 수많은 상태 클래스를 열어봐야 한다는 단점이 있습니다.
3. 뽑기 기계 인스턴스가 여러 개인 경우를 고려해야 합니다.
상태 인스턴스는 정적 인스턴스(싱글턴) 변수로 만들어 공유하는 것이 좋습니다.
상태 패턴은 아주 적은 수의 상태만 있거나, 상태가 변경되더라도 머신의 행동은 크게 변경되지 않는 경우에 적용하면 과도할 수도 있습니다. 조건문을 제거하고 SRP를 준수한다는 장점은 있으나 클래수의 수가 너무 많아진다는 단점이 있기 때문입니다.
- Total
- Today
- Yesterday
- 쇼미더코드
- 백준27219
- 리눅스cron
- whatis
- baekjoon
- 코테
- atq
- linuxtouch
- OnActivityForResult
- awk프로그램
- 사용자ID
- 백준
- linuxgedit
- api문서
- E_FAIL
- linuxawk
- 리눅스
- 백준27211
- 버추억박스오류
- virtualbox
- GitHubAPIforJava
- 버추억박스에러
- Baekjoon27219
- cron시스템
- Baekjoon27211
- SELECT #SELECTFROM #WHERE #ORDERBY #GROUPBY #HAVING #EXISTS #NOTEXISTS #UNION #MINUS #INTERSECTION #SQL #SQLPLUS
- Linux
- GithubAPI
- cat
- linux파일
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |