2011년 5월 23일 월요일

The State Pattern

20. The State Pattern
State 패턴은 개발자가 객체를 애플리케이션의 상태를 표현하게 할 수 있도록 하며, 객체의 상태를 전환함으로써 애플리케이션의 상태도 전환할 수 있게 한다. 예를 들어, 다수의 관련된 내장 클래스 사이에서 전환하도록 동봉(enclosing) 클래스를 설정하거나 현재의 내장 클래스의 메소드 호출을 전달하도록 한다. 디자인 패턴에서는 state 패턴이 동봉된 객체가 해당 클래스를 변경하는 것처럼 보이는 방식으로 내부 클래스 사이를 state 패턴이 전환하도록 권장한다. 적어도 자바에서는 이 방법이 약간 과장되었지만, 어느 클래스가 사용되는지에 대한 실제적인 목적이 분명한 변화를 초래할 수 있다.
많은 개발자들이 미묘하게 다른 연산을 수행하거나 클래스에 전달되는 아규먼트에 따라 다양한 정보를 출력하는 클래스를 생성해 본 경험을 갖고 있다. 이런 경험을 종종 특정 유형의 전환 작업 및 어느 작업을 수행할지 결정하는 클래스 내부의 if-else문을 이끌어내기도 한다. 이런한 비효율성이 바로 state 패턴을 대체해야 하는 이유이다.

구조


역할
· Context : 클라이언트들이 원하는 인터페이스를 정의한다. 현재 상태를 정의하는 ConcreteState 서브클래스의 인스턴스를 가진다.
· State : Context 의 특정 상태와 관련된 행위들을 캡슐화 하기 위한 관련 인터페이스를 정의한다.
· ConcreteState : 각각의 서브클래스들은 Context의 상태들과 관련된 행위들을 구현한다.

의도
객체의 내부 상태가 바뀌었을 때 객체의 행동을 바꿀 객체를 허용한다. 객체는 마치 객체의 클래스가 변경된 것처럼 보일 것이다.

적용시기
  • 객체의 행위가 객체의 상태에 의존적일때. 그리고 객체가 run-time 시에 상태에 따라 행위를 바꾸어야 할 경우.
  • 객체의 상태에 대한 처리를 위해 구현하는 다중 조건 제어문이 거대해질 경우. 이 상태들을 일반적으로 하나나 그 이상의 열거형 상수들로 표현된다. 종종 여러 명령들은 객체 상태에 따른 처리를 위해 비슷한 유형의 조건 제어와 관련한 코드를 가지게 된다. State 패턴은 각각의 조건분기점들을 클래스로 분리시킨다. 이는 객체의 상태들을 다른 객체로부터 다양하게 독립적일 수 있는, 고유의 권리를 가지는 객체로서 취급하도록 해준다.


결론
· State 패턴은 애플리케이션이 보유하게 될 각각의 기본 state 정보별 기본 state 객체의 서브 클래스를 생성하고, 해당 클래스들 간의 작업을 애플리케이션이 상세 정보를 변경할 때마다 수행한다.
· 각각의 State 정보는 클래스 안에 캡슐화되어 있기 때문에 다양한 State 정보와 관련된 다수의 if및 switch 조건문을 포함하지 않아도 된다.
· 프로그램이 어떤 상태값으로 설정되어 있는지 구체적으로 명시하는 변수는 없다. 그러므로 이런 방식은 해당 State 변수의 확인 작업을 생략한 개발자에 의해 발생하는 오류를 감소시킨다.
· 별도의 창과 같은 애플리케이션의 각 요소 간의 State 객체들을 어떤 종류의 State 객체도 구체적으로 인스턴스 변수를 갖지 않는 한 공유할 수 있다.
· 자바 언어에서 모든 State객체는 공통 기본 클래스에서 상속되어야 하며, 모든 객체는 공통의 메소드를 보유해야 한다. 이런 조건은 해당 메소드 중 일부가 빈 공간으로 남아도 지켜줘야 한다. 다른 개발 언어에서 State 패턴은 함수 포인터가 구현하는데, 형 검사 작업의 횟수가 크게 감소하는 문제 때문에 오류 발생 빈도가 높아진다.

예제소스


예제 소스
public interface Context {
public abstract void setClock(int hour);
public abstract void changeState(State state);
public abstract void callSecurityCenter(String msg);
public abstract void recordLog(String msg);
}
public 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);
}
public class DayState implements State {
private static DayState singleton = new DayState();
private DayState() {
}
public static State getInstance() {
return singleton;
}
public void doClock(Context context, int hour) {
if (hour < 9 || 17 <= hour) {
context.changeState(NightState.getInstance());
}
}
public void doUse(Context context) {
context.recordLog("금고사용(주간)");
}
public void doAlarm(Context context) {
context.callSecurityCenter("비상벨(주간)");
}
public void doPhone(Context context) {
context.callSecurityCenter("일반 통화(주간)");
}
public String toString() {
return "[주간]";
}
}
public class NightState implements State {
private static NightState singleton = new NightState();
private NightState() {
}
public static State getInstance() {
return singleton;
}
public void doClock(Context context, int hour) {
if (9 <= hour && hour < 17) {
context.changeState(DayState.getInstance());
}
}
public void doUse(Context context) {
context.callSecurityCenter("비상:야간의 금고사용");
}
public void doAlarm(Context context) {
context.callSecurityCenter("비상벨(야간)");
}
public void doPhone(Context context) {
context.recordLog("야간의 통화 녹음");
}
public String toString() {
return "[야간]";
}
}
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.Button;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.Panel;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
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);
textScreen.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);
}
public void actionPerformed(ActionEvent e) {
System.out.println("" + e);
if (e.getSource() == buttonUse) {
state.doUse(this);
} else if (e.getSource() == buttonAlarm) {
state.doAlarm(this);
} else if (e.getSource() == buttonPhone) {
state.doPhone(this);
} else if (e.getSource() == buttonExit) {
System.exit(0);
} else {
System.out.println("?");
}
}
public void setClock(int hour) {
String clockstring = "현재 시각은";
if (hour < 10) {
clockstring += "0" + hour + ":00";
} else {
clockstring += hour + ":00";
}
System.out.println(clockstring);
textClock.setText(clockstring);
state.doClock(this, hour);
}
public void changeState(State state) {
System.out.println(this.state + "에서" + state + "로 상태가 변화했습니다.");
this.state = state;
}
public void callSecurityCenter(String msg) {
textScreen.append("call! " + msg + "\n");
}
public void recordLog(String msg) {
textScreen.append("record ... " + msg + "\n");
}
}
public class mainClass extends Thread {
public static void main(String[] args) {
SafeFrame frame = new SafeFrame("State Sample");
while (true) {
for (int hour = 0; hour < 24; hour++) {
frame.setClock(hour);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
}


관련패턴
  • Singleton 패턴 : ConcreteState 역할은 Singleton 패턴으로 구현되는 경우가 있다.
  • Flyweight 패턴 : 상태를 나타내는 클래스는 인스턴스 필드를 가지지 않는다. 따라서 Flyweight 패턴을 사용해서 ConcreteState 역할을 다수의 Context 역할에서 공유할 수 있는 경우도 있다.

댓글 없음:

댓글 쓰기

블록체인 개요 및 오픈소스 동향

블록체인(block chain) 블록체인은 공공 거래장부이며 가상 화폐로 거래할때 발생할때 발생할 수 있는 해킹을 막는 기술. 분산 데이터베이스의 한 형태로, 지속적으로 성장하는 데이터 기록 리스트로서 분산 노드의 운영자에 의한 임의 조작이 불가...