State는 코드내의 상태를 클래스로 분류하여 상태에 따라 다른 실행결과가 만들게 해주는 패턴이다.
얘기만 들어보면 이전에 공부했던 전략패턴이랑 비슷한 느낌이 든다. 하지만 둘에게는 현저히 다른 차이점이 있다.
State 패턴
클라이언트가 아닌 코드 내부의 분기에 의해서 상태가 변한다.
strategy 패턴
클라이언트가 전략을 선택하여 코드를 실행시킬 수 있다.
상태를 나누면 가져올 장점이 무엇이 있을까?
특정한 문제를 한번에 다 풀려고 한다면 큰 어려움이 발생합니다. 분할해서 코딩을 하게되면 작은문제 부터 차근차근 해결하게됩니다. 여기서도 분할없이 사용하게 된다면 if Else문을 통해서 엄청나게 긴 분기가 발생할 수 있습니다.
상태에 의존한 처리
아직 예제 코드를 작성하지 않았지만 상태 객체에게 의존하여 코드가 실행됩니다. -> 상태에 따라서 동작이 달라지는 상황이 생깁니다.
예제 프로그램
은행을 경비하는 awt 프로그램을 만들 것입니다.
상태는 주간, 야간, 점심시간이고
주간 시간에 금고에 손을 대는 것과 경비센터에 전화를 하는 것과 전형 다른 실행 결과를 가져올 것입니다.
UML
State
interface State{ public abstract void doClock(Context context, int hour); public abstract void doUse(Context context); public abstract void doAlarm(Context context); public abstract void doPhone(Context context); }
아래 state 세개에서는 객체를 계속 만들어 주는 것이 성능적인 부분에서 문제가 될 수 있다고 생각하여 싱글톤 패턴을 활용하였습니다.
doClock은 조건에 맞게 상태를 변환해줍니다.
LaunchState.java
public class LaunchState implements State{ private static LaunchState singleton = new LaunchState(); private LaunchState(){} public void doClock(Context context, int hour){ if(hour==13) context.changeState(DayState.getInstance()); } public void doUse(Context context){ context.callSecurity("비상: 점심시간금고 사용!"); } public void doAlarm(Context context){ context.callSecurity("비상벨(점심)"); } public void doPhone(Context context){ context.callSecurity("경비센터 자동응답기 호출"); } public static LaunchState getInstance(){ return singleton; } public String toString(){ return "[점심시간]"; } }
NightState.java
public class NightState implements State{ private static NightState singleton = new NightState(); private NightState(){}
public void doClock(Context context, int hour){ if(9 <= hour && hour<17) context.changeState(DayState.getInstance()); } public void doUse(Context context){ context.callSecurity("비상: 야간금고 사용!"); } public void doAlarm(Context context){ context.callSecurity("비상벨(야간)"); } public void doPhone(Context context){ context.recordLog("야간통화 녹음"); }
public static NightState getInstance(){ return singleton; } public String toString(){ return "[야간]"; } }
DayState.java
public class DayState implements State{ private static DayState singleton = new DayState(); private DayState(){}
public void doClock(Context context, int hour){ if(hour<9 || hour>=17) context.changeState(NightState.getInstance()); else if(hour==12) context.changeState(LaunchState.getInstance()); } public void doUse(Context context){ context.recordLog("금고 사용(주간)"); } public void doAlarm(Context context){ context.callSecurity("비상벨(주간)"); } public void doPhone(Context context){ context.callSecurity("일반통(주간)"); } public static DayState getInstance(){ return singleton; } public String toString(){ return "[주간]"; } }
SafeFrame.java
awt를 만드는 코드라서 조금 깁니다...
import java.awt.*; import java.awt.event.*; public class SafeFrame extends Frame implements ActionListener, Context{ private TextField textClock = new TextField(60); private TextArea textScreen = new TextArea(10, 60); private Button buttonUse = new Button("금고사용"); private Button buttonAlarm = new Button("비상벨"); private Button buttonPhone = new Button("일반통화"); private Button buttonExit = new Button("종료"); private State state = DayState.getInstance(); public SafeFrame(String title){ super(title); setBackground(Color.lightGray); setLayout(new BorderLayout()); add(textClock, BorderLayout.NORTH); textClock.setEditable(false); add(textScreen, BorderLayout.CENTER); textClock.setEditable(false); Panel panel = new Panel(); panel.add(buttonUse); panel.add(buttonAlarm); panel.add(buttonPhone); panel.add(buttonExit); add(panel, BorderLayout.SOUTH); pack(); show(); buttonUse.addActionListener(this); buttonAlarm.addActionListener(this); buttonPhone.addActionListener(this); buttonExit.addActionListener(this); }