프로그래밍 Design Pattern 이해하기 - 10 스테이트 패턴


Android/번역본 2014.09.28 19:01



스테이트 패턴 


오늘은 실제로 사람들이 쓰는 기계를 자바로 만들어보려고 한다. gumball machine(우스꽝스러운 기기)를 생각보다 높은 기술력을 가지고 있다. gumball machine 이란 동전넣고 돌리면 껌이 나오는 기기다. 아래 다이어그램을 보자!







한글로 번역하면,


 

 이것은 검볼머신 컨트롤로가 필요로 하는 다어그램이다. 우리는 미래에 어떤 행동이 추가 될것인지 미리 생각을 해야 한다. 그래서 최대한 유동적이고 유지가 쉽도록 구조를 만들어야 한다. 위를 잘보면 동전을 투입한 후, 손잡이를 돌린다. 돌리면, 알맹이가 있으면 판매를 하고 알맹이의 갯수를 확인한다. 알맹이의 갯수가 0이라면, 매진처리가 된다. 


 이 다이어그램은 스테이트 다이어그램으로 볼 수 있다. 각각의 동그라미가 State가 된다. 그리고 화살표가 각각의 state의 transition을 나타낸다. 최초의 상태는 '동전 없음'에서 시작한다. 사용자가 동전을 넣으면 state가 변경되고, 동전있음 상태로 변한다. 동전있음 상태에서 동전을 반환하거나 혹은 손잡이를 돌리는 것에 따라서 화살표가 나뉘어 진행된다. 


 자 그럼, State를 나눠보자. 


"동전 없음" "동전있음" "알맹이 매진" "알맹이 판매" 이렇게 네가지의 상태로 나눌 수 있겠다. 


이 상태들을 상수로 정의하자.


final static int SOLD_OUT = 0;

final static int NO_QUARTER = 1;

final static int HAS_QUATER = 2;

final static int SOLD = 3;


int state = SOLD_OUT;


위에는 상수로 각 상태를 정의했고, 아래에 보이는 state 변수에 이 상수값들을 넣으면서 사용한다. 최초에 이 기계를 박스에서 꺼낼 때에는 SOLD_OUT 상태이기 때문에 최초값은 SOLD_OUT으로 둔다. 


 자 그럼 액션들을 만들어보자. 


동전 투입, 동전 반환, 손잡이 돌림, 알맹이를 내보냄

insert Quater, ejects quater, turns crank, dispense


이제부터 머신에 들어갈 클래스를 만들어보자 

각각의 상태에 맞도록 상태를 출력해보자. 


public void insertQuater() {

if(state == HAS_QUATER)

System.out.println("더이상 동전을 넣을 수 없어요");

else if(state == SOLD_OUT)

System.out.println("더이상 동전을 넣을 수 없어요. 매진입니다");

else if(state == SOLD)

System.out.println("잠시만 기다리세요. 알맹이를 내보내고 있습니다!");

else if(state == NO_QUARTER)

state = HAS_QUARTER;

System.out.println("동전이 들어왔어요!");

}


위 함수는 말 그대로 동전이 투입되었을 때 코드이다. 




여기까지 검볼 머신의 코드를 만들었다. 말 그대로 차례차례 메소드가 호출되며 각 상태에 맞도록 state를 변경하고 메세지를 출력한다. 

Test Drive 코드를 보자. 







이전의 패턴

 ---------------------------------

자 그럼 여기에 스테이트 패턴을 적용해보자. 


1. 뽑기 기계와 관련된 모든 행동에 대한 메소드가 들어있는 State interface를 정의한다. 

2. 기계의 모든 상태에 대한 상태 클래스를 만든다. 

3. 마지막으로 조건문을 없애고 상태 클래스에게 작업을 위임한다. 


코드로 보자!


먼저 State Interface 를 만들자. 




State interface는 state가 콩크리트 클래스들이 받을 interface며 위의코드에서 만들었던 '메소드(액션)'들을 가지고 있다. 



SoldOutState.java


NoQuarterState.java



HasQuarterState.java



SoldState.java




이 코드들을 가지고 GumballMachineTestDrive 코드를 보자!


GumballMachine.java




기존에 int형으로 가지고 클래스안에서 상태에 따라 조건문을 통해 보지도 못할 정도로 복잡하게 구현되어 있던 것들이 State클래스에게 작업을 위임하니 깔끔해질뿐더러 수정에 따른 영향을 미칠 정도도 많이 줄였다. 





state가 작업을 위임받아 함수를 직접 구현하기 때문에 Localized 가 되었다!

그리고, 수없이 많았던 if 조건문을 줄여 유지를 보다 쉽게 할 수 있게 되었다. 

수정에는 닫혀있고, 확장에는 열려있게 되었다.(새로운 State를 추가할 때 새로운 클래스만 만들어 함수를 구현하고 넣어주면 끝!)

좀 더 쉽게 다이어그램을 그릴 수 있고 이해하기 쉬워졌다.




 스테이트 패턴은 객체가 곧 상태이며, 행동을 들고있다. 변화가 생길 때 클래스로 변화를 관리한다. 가 핵심이다. 


위 그림을 따라, State interface가 있고, 해당 State를 구체적으로 구현한 ConcreateState클래스들이 있다. 

그리고 실제 코드에서는 이 상태 클래스에게 액션을 취하면 된다. 



여기서 잠깐! 우리가 첫시간에 배웠던 스트래피지 패턴을 기억할지 모르겠다.  


Duck을 만들고, 그 Duck클래스를 상속받아 ConcreateDuck들을 만들고 코드에서는 Duck에 기재된 함수를 호출했다. 정확히 일치한다! 

 다만 다른 점은, 스트레티지 패턴은, 각각의 ConcreateClass를 각각 제어하며, 스테이트 패턴은 ConcreateClass중 하나를 상태로 저장하며 현재 상태인 ConcreateClass를 사용한다는 점이 다르다. 


다시 한번 정리하면, 


- 스테이트 패턴을 이용하면 내부 상태를 바탕으로 서로 다른 행동이 가능하다. 

- 각 상태를 클래스를 이용해 표현한다. 

- 각 상태에 행동을 위임한다. 

- 각 상태를 캡슐화함으로써 나중에 변경되는 함수를 국지화할 수 있다. 

- 스테이트 패턴과 스트래티지 패턴의 다이어그램은 똑같다.

- 스트레티지 패턴에서는 행동또는 알고리즘을 클라이언트코드에서 설정한다. 

- 상태 전환은 state클래스에서 제어할 수도 있고, 클라이언트코드에서도 제어할 수 있다. 

- 클래스의 갯수가 늘어나는 단점이 있다.








저작자 표시 비영리 변경 금지
신고

WRITTEN BY
ShakeJ

0 ,