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정보와 관련된 다수의ifswitch조건문을 포함하지 않아도 된다.

·         프로그램이 어떤 상태값으로 설정되어 있는지 구체적으로 명시하는 변수는 없다.그러므로 이런 방식은 해당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) {

               }

           }

       }

   }

}

 

관련패턴

l       Singleton패턴: ConcreteState역할은Singleton패턴으로 구현되는 경우가 있다.

l       Flyweight패턴:상태를 나타내는 클래스는 인스턴스 필드를 가지지 않는다.따라서Flyweight패턴을 사용해서ConcreteState역할을 다수의Context역할에서 공유할 수 있는 경우도 있다.

'Programming > Design Pattern' 카테고리의 다른 글

[펌] The Observer Pattern  (0) 2006.01.21
[펌] The Observer Pattern  (0) 2006.01.21
[펌] The Template Pattern  (0) 2006.01.21
[펌] The Visitor Pattern  (0) 2006.01.21
[펌] The Interpreter Pattern  (0) 2006.01.21

Posted by 영웅기삼
,