[JAVA 디자인 패턴 정리] 3. 상태(Stat) 패턴

[JAVA 디자인 패턴 정리] 3. 상태(Stat) 패턴

단일 상품을 판매하는 자판기에 들어갈 소프트웨어를 개발해 달라는 요구.

요구사항

동작 조건 실행 결과 동전 넣음 동전 없으면 금액을 증가 제품 선택 가능 동전 넣음 제품 선택했으면 금액을 증가 제품 선택 가능 제품 선택 동전 없으면 아무 동작하지 않는다 동전 없음 유지 제품 선택 제품 선택했으면 제품 제공하고 잔액 감소 잔액 있으면 제품 선택 가능,

잔액 없으면 동전 없음

조건문을 통한 자판기 프로그램

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 구현 } Colored by Color Scripter 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() } Colored by Color Scripter 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; } ... // 기타 다른 기능 } Colored by Color Scripter 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(); } } Colored by Color Scripter 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()); } } Colored by Color Scripter 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(); } ... // 기타 다른 기능 } Colored by Color Scripter 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(); } } Colored by Color Scripter cs

콘텍스트의 상태 변경은 누가 할지는 주어진 상황에 맞게 정해야 한다.

콘텍스트에서 상태를 변경하는 방식은 비교적 상태 개수가 적고 상태 변경 규칙이 거의 바뀌지 않는 경우 유리하다.

상태 객체에서 상태를 변경하는 방식은 콘텍스트에 영향을 주지 않으면서 해야한다

상태 구현 클래스가 많아질수록 상태 변경 규칙을 파악하기 어려워지는 단점이 있기에 상황에 맞게 잘 써야 한다.

출처:[교재] 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴

from http://pulpul8282.tistory.com/224 by ccl(A) rewrite - 2021-12-07 10:27:27