
본 교재를 기반으로 정리합니다.
단일 상품을 판매하는 자판기에 들어갈 소프트웨어를 개발해 달라는 요구.
요구사항
동작 | 조건 | 실행 | 결과 |
동전 넣음 | 동전 없으면 | 금액을 증가 | 제품 선택 가능 |
동전 넣음 | 제품 선택했으면 | 금액을 증가 | 제품 선택 가능 |
제품 선택 | 동전 없으면 | 아무 동작하지 않는다 | 동전 없음 유지 |
제품 선택 | 제품 선택했으면 | 제품 제공하고 잔액 감소 | 잔액 있으면 제품 선택 가능, 잔액 없으면 동전 없음 |
조건문을 통한 자판기 프로그램
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
31
32
|
public class VendingMachine {
public static enum State { NOCOIN, SELECTABLE }
private State state = State.NOCOIN;
public void insertCoin(int coin) {
switch(state) {
case NOCOIN:
increaseCoin(coin);
state = State.SELECTABLE;
break;
case SELECTABLE:
increaseCoin(coin);
}
}
public void select(int productId) {
switch(state) {
case NOCOIN:
//아무 것도 하지않음
break;
case SELECTABLE:
provideProduct(productId);
decreaseCoin();
if(hasNocoin())
state = State.NOCOIN;
}
}
...// increaseCoin, provideProduct, decreaseCoin 구현
}
|
cs |
새로운 요구사항:
자판기에 제품이 없는 경우 동전을 넣으면 바로 동전을 돌려준다
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public class VendingMachine {
public static enum State { NOCOIN, SELECTABLE, SOLDOUT /* 새로 추가 */ }
private State state = State.NOCOIN;
public void insertCoin(int coin) {
switch(state) {
case NOCOIN:
increaseCoin(coin);
state = State.SELECTABLE;
break;
case SELECTABLE:
increaseCoin(coin);
}
}
public void select(int productId) {
switch(state) {
case NOCOIN:
increaseCoin(coin);
state = State.SELECTABLE;
break;
case SELECTABLE:
increaseCoin(coin);
break;
/*새로 추가*/
case SOLDOUT:
returnCoin();
}
}
public void select(int productId) {
switch(state) {
case NOCOIN:
//아무 것도 하지 않음
break;
case SELECTABLE:
provideProduct(productId);
decreaseCoin();
if(hasNoCoin())
state = State.NOCOIN;
case SOLDOUT:
//아무 것도 하지 않음
}
}
... // increaseCoin, provideProduct, decreaseCoin. returnCoin()
}
|
cs |
public void insert(int coin){
switch(state){
case NOCOIN:
increaseCoin(coin);
state = State.SELECTABLE;
break;
case SELECTABLE:
increaseCoin(coin);
break;
case SOLDOUT:
}
}
위 코드를 보면 동전 사입 기능이 상태에 따라서 다르게 동작한다.

상태 패턴에서는 상태 객체가 기능을 제공한다.
State 인터페이스는 동전 증가 처리와 제품 선택 처리를 하는 두 개의 메서드를 정의하고 있다.
콘텍스트는 필드로 상태 객체를 갖고 있다.
콘텍스트는 클라이언트로 부터 기능 실행 요청을 받으면, 상태 객체에 처리를 위임하는 방식으로 구현한다.
VendingMachine 클래스의 insertCoin() 메서드와 select() 메서드를 보며 State 객체에 처리를 위임하는 것을 확인하자.
상태패턴 적용한 VendingMachine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class VendingMachine {
private State state;
public VendingMachine() {
state = new NoCoinState();
}
public void insertCoin(int coin) {
state.increaseCoin(coin, this);//상태 객체에 위임
}
public void select(int productId) {
state.select(productId, this);//상태 객체에 위임
}
private void changeState(State newState) {
this.state = newState;
}
... // 기타 다른 기능
}
|
cs |
동전 없음 상태일 때의 동작을 구현한 NoCoinState 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class NoCoinState implements State {
@Override
public void increaseCoin(int coin, VendingMachine vm) {
vm.increaseCoin(coin);
//상태 객체에서 콘텍스트의 상태 변경 vm.changeState(new SelectableState());
}
@Override
public void select(int productId, VendingMachine vm) {
SoundUtil.beep();
}
}
|
cs |
|
제품 선택 가능 상태의 동작을 구현한 SelectableState 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class SelectableState implements State {
@Override
public void increaseCoin(int coin, VendingMachine vm) {
vm.increaseCoin(coin);
}
@Override
public void select(int productId, VendingMachine vm) {
vm.provideProduct(productId);
vm.decreaseCoin();
if(vm.hasNoCoin())
vm.changeState(new NoCoinState());
}
}
|
cs |
위의 두 클래스는 상태 패턴을 적용함으로써 VendingMachine 클래스에 구현되어 있는 상태 별 동작 구현 코드가 각 상태의 구현 클래스로 이동함을 확인 할 수 있다. 이런식으로 상태 객체에 위임한다.
상태 패턴의 장점은 새로운 상태가 추가되더라도 콘텍스트 코드가 받는 영향은 최소화 된다.
상태가 많아지더라도 코드의 복잡도는 증가하지 않기 때문에 유지 보수에 유리하다.
상태변경?

상태 객체에서 콘텍스트의 상태를 변경하려면 콘텍스트의 다른 값에 접근해야 할 때도 있다.

콘텍스트 VendingMachine에서 상태를 변경하도록 구현한 예
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
31
32
33
34
35
36
|
public class VendingMachine {
private State state;
public VendingMachine() {
state = new NoCoinState();
}
public void insertCoin(int coin) {
state.increaseCoin(coin, this);//상태 객체에 위임
if(hasCoin())
changeState(new SelectableState());// 콘텍스트에서 상태변경
}
public void select(int productId) {
state.select(productId, this);//상태 객체에 위임
if(state.isSelectable() && hasNoCoin())
changeState(new SelectableState());// 콘텍스트에서 상태변경
}
private void changeState(State newState) {
this.state = newState;
}
private boolean hasCoin() {
...
}
private boolean hasNoCoin() {
return ! hasCoin();
}
... // 기타 다른 기능
}
|
cs |
콘텍스트가 상태 변경을 하므로 상태 객체는 자신이 수행해야 하는 작업만 처리하도록 바뀐다.
1
2
3
4
5
6
7
8
9
10
|
public class SelectableState implements State{
@Override
public void select(int productId, VendingMachine vm) {
vm.provideProduct(productId);
vm.decreaseCoin();
}
}
|
cs |
콘텍스트의 상태 변경은 누가 할지는 주어진 상황에 맞게 정해야 한다.
콘텍스트에서 상태를 변경하는 방식은 비교적 상태 개수가 적고 상태 변경 규칙이 거의 바뀌지 않는 경우 유리하다.
상태 객체에서 상태를 변경하는 방식은 콘텍스트에 영향을 주지 않으면서 해야한다
상태 구현 클래스가 많아질수록 상태 변경 규칙을 파악하기 어려워지는 단점이 있기에 상황에 맞게 잘 써야 한다.
출처:[교재] 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴