본문 바로가기

Dev.../패턴자료

The State Pattern

The State Pattern

    State 패턴은 관련된 다수의 클래스들 사이에서 대체할 수 있는 하나의 포괄적인 클래스를 갖고자 하고 현재 포함된 클래스를 호출하는 메소드를 전달하고자 할 때 이용된다. 디자인 패턴은 클래스를 바꾸기 위해 나타나는 포괄적인 객체와 같은 식으로 내부의 클래스들 사이를 대체하는데 State 패턴을 제안하였다. 자바에서는 조금 과장되었지만 실제적인 목적은 클래스들이 의미 있게 변경될 수 있도록 하는데 있다.

    많은 프로그래머들은 약간 차이가 있는 계산이나 클래스에 전달된 아규먼트들을 토대로 한 다른 정보를 보여주는 것을 수행하는 클래스를 생성한 경험이 있다. 이것은 종종 수행해야 할 행위를 결정짓기 위해 클래스 내부에 switch 나 if-else 와 같은 문장의 종류를 이끌게 한다. 그것은 State 패턴이 대체하는 것을 찾는 안 좋은 방법이다.

Sample Code

    Memento 클래스를 위하여 개발했던 것과 유사한 드로잉 프로그램의 경우를 고려해 보자. 프로그램은 Select, Rectangle, Fill, Circle 와 Clear를 위한 툴바를  갖게 될 것이다.

       

    버튼 각각은 선택되고 화면에서 마우스를 클릭하거나 드래그 할 때  다른 일을 한다. 그러므로 그래픽 편집기의 상태는 프로그램이 진행되는 행위에 영향을 준다. 이것이 State 패턴을 이용한 몇 가지 설계를 제안한다.

    처음에는 우리의 프로그램을 5개의 명령 버튼들의 행동을 관리 하는 Mediator를 갖는 아래와 같은 프로그램을  설계할 수 있다.

       

    그러나 처음의 설계는 Mediator에서 프로그램의 상태를 유지하는 전체적인 부하를 두게 되고, Mediator의 중요 목적은 다양한 컨트롤들 사이에서 활동들을 조정하는 것이다. 버튼들의 상태와 Mediator 내에서 요구되는 마우스의 활동을 유지하는 것은 프로그램을 읽고 유지하는 것을 어렵게 하는 if 나 switch 를 유도하게 되어 과도하게 복잡하게 만든다.

    게다가 거대한 조건문 집단은 각 Mediator가 mouseUp, mouseDrag, rightClick 등과  같은 해석하는 동작에 대해서 반복해야만 할 수도 있다. 이것이 프로그램을 일기 어렵게 하고 유지하는 걸 어렵게 만든다.

    대신, 각 버튼들에 대해서 기대되는 행위를 분석해 보자 :
  1. Pick 버튼이 선택되었다면 드로잉 원소 내부에서의 clicking은  하이라이트 되거나 "handles"로 나타날 수 있다. 마우스가 드래그 되고 드로잉 원소가 이미 선택되었다면 그 원소는 화면상에서 움직일 수 있다.
     
  2. Rect 버튼이 선택되면 화면상에 clicking은 새로운 사각형 드로잉 원소를 생성할 수 있게 한다
     
  3. Fill 버튼이 선택되어 있고 드로잉 원소가 이미 선택되어 있으면, 그 원소는 현재 색으로 채워질 것이다. 만약 드로잉 선택되어 있지 않으면 드로잉 원소 내부를 클릭할 때 현재 색으로 채울 것이다.
     
  4. Circle 버튼이 선택되었다면 화면상에 클릭은 새로운 원 원소를 생성할 것이다.
     
  5. Clear 버튼이 선택되었다면 모든 드로잉 원소는 제거될 것이다.

    우리가 조사한 몇 가지 동작들 사이에는 몇 가지 공통적인 맥락이 있다. 그런 것들 중 네 가지는 마우스 클릭을 이용한다. 하나는 마우스 드래그 이벤트를 사용한다. 그러므로 우리는 실제로 버튼이 현재 선택되었는지를 토대로 이러한 이벤트를 다시 돌릴 수 있도록 하는 시스템을 만들고자 한다.
   
    마우스 동작을 조정하는 State 객체를 만들어 보자:

public class State {	public void mouseDown(int x, int y){}	public void mouseUp(int x, int y){}	public void mouseDrag(int x, int y){}}
    우리는 나중에 필요할 지도 모르기 때문에 mouseUp 이벤트를 포함할 것이다. 이러한 이벤트 모두의 필요성을  설명한 적이 없기 때문에 추상의 base 클래스를 생성하는 것 보다 메소드가 비어 있는 base 클래스를 만들 것이다. 그리고 나서 Pick, Rect, Circle 과 Fill을 위해 State 클래스로부터 4개를 파생시켜 생성할 것이고 현재 상태를 정하는 StateManager 클래스에 모두를 인스턴스로 넣고 state 객체상의 메소드들을 실행할 것이다. 디자인 패턴에서 StateManager 클래스는 Context로 참조된다. 이 객체는 아래와 같이 설명된다 :

       

    전형적인 State 객체는 간단히 특별하게 조정해야 하는 이벤트 메소드들을 재정의한다. 
public class RectState extends State {	private Mediator med;     //save the Mediator here		public RectState(Mediator md) {		med = md;	}		//create a new Rectangle where mode clicks	public void mouseDown(int x, int y) {		med.addDrawing(new visRectangle(x, y));	}}
    RectState 객체는 간단하게 Mediator에게 드로잉 목록에 사각형 드로잉을 추가할 것을 알려준다. 유사하게, Circle state 객체도 Mediator에게 드로잉 목록에 circle을 추가하도록 알려준다 :
public class CircleState extends State {	private Mediator med;        //save Mediator		public CircleState(Mediator md) {		med = md;	}		//Draw circle where mouse clicks	public void mouseDown(int x, int y) {		med.addDrawing(new visCircle(x, y));	}}
    우리는 다음 두 가지 행동에 대해 정의를 했기 때문에 Fill 버튼은 교묘한 버튼이다.
  1. 이미 선택된 객체가 있다면 그것을 채워라
     
  2. 객체 내부에서 마우스가 클릭되면 그 객체를 채워라

    이러한 작업을 수행하기 위해 우리는 base State 클래스에 select 메소드를 추가하는 것이 필요하다. 이 메소드는 각각의 툴 버튼이 선택되면 호출되어 진다 :

public class State {	public void mouseDown(int x, int y){}	public void mouseUp(int x, int y){}	public void mouseDrag(int x, int y){}	public void select(Drawing d, Color c){}}
 드로잉 아규먼트는 현재 선택된 드로잉이나 선택되지 않았다면 null이고 색은 현재 채워진 색이다. 이 프로그램에서 우리는 임으로 채워지는 색을 빨강색으로 정하였다 :
public class FillState extends State {	private Mediator med;   //save Mediator	private Color color;    //save current color		public FillState(Mediator md) {		med = md;	}		//Fill drawing if selected	public void select(Drawing d, Color c) {		color = c;				if(d!= null) {			d.setFill(c);  //fill that drawing		}	}		//Fill drawing if you click inside one	public void mouseDown(int x, int y) {		Vector drawings = med.getDrawings();				for(int i=0; i< drawings.size(); i++) {			Drawing d = (Drawing)drawings.elementAt(i);			if(d.contains(x, y))				d.setFill(color); //fill drawing		}	}}

Switching Between States

    지금까지 우리는 마우스 이벤트가 보내질 때 각 상태는 어떻게 행동하는지를 정의하였고 어떻게 StateManager가 상태들을 바꾸는지 논의할 필요가 있다; 우리는 선택된 버튼에 의해 지시되는 상태로 currentState 변수를 정하였다.
import java.awt.*;public class StateManager {	private State currentState;	RectState rState;	ArrowState aState;	CircleState cState;	FillState fState;		public StateManager(Mediator med) {		rState = new RectState(med);		cState = new CircleState(med);		aState = new ArrowState(med);		fState = new FillState(med);		currentState = aState;	}		public void setRect() {		currentState = rState;	}		public void setCircle() {		currentState = cState;	}		public void setFill() {		currentState = fState;	}		public void setArrow() {		currentState = aState;	}
    StateManager에서 우리는 생성자에서 각 상태의 인스턴스를 생성하고  set 메소드가 호출될 때 상태 변수에 정확한 인스턴스를 저장한다. 요구에 따라 이 상태들을 생성하기 위해  Factory를 사용하는 것도 가능하다.

    StateManager의 나머지 코드는 간단하게 현재 state 객체가 어떤 것이건 간에 메소들을 호출한다. 이 부분이 결정적인 부분이다 -- 거기에는 어떤 조건적인 검사가 없다. 대신 정확한 상태는 이미 정해져 있고 그것의 메소들은 이미 호출될 준비가 되어 있다.

How the Mediator Interacts with the State Manager

우리는 Mediator의 버튼과 마우스 이벤트 관리로부터 state 관리를 분리하는 것이 명확하다는 것을 언급했다. Mediator는 결정적인 클래스이지만 현재 프로그램 상태가 변경되면 StateManager에게 알려준다. Mediator 의 시작 부분은 어떻게 이 상태 변경을 처리할 수 있는가를 설명한다:

public Mediator() {	startRect = false;	dSelected = false;	drawings = new Vector();	undoList = new Vector();	stMgr = new StateManager(this);}//-------------------------------------------public void startRectangle() {	stMgr.setRect();	arrowButton.setSelected(false);	circButton.setSelected(false);	fillButton.setSelected(false);}//---------------------------------------------public void startCircle() {	stMgr.setCircle();	rectButton.setSelected(false);	arrowButton.setSelected(false);	fillButton.setSelected(false);}
이러한 startXxx 메소들은 Command 객체로서 각 버튼의 Execute 메소드들로부터 호출되어 진다.

Consequences of the Observer Pattern

  1. State 패턴은 각 상태에 대한 개별적인 클래스에서 state-specific 행위로 제한하고 하나의 객체에서 상태를 위한 모든 행동을 넣는다.
     
  2. 프로그램의 코드를 통하여 산재되어 있는 조건문과 같은 거에 대한 필요성을 제거해 준다.
     
  3. 뚜렷한 트랜지션을 만든다.
     
  4. State 객체들은 인스턴스 변수가 없다면 공유되어 질 수 있다. 여기서는 Fill 객체만 인스턴스 변수를 가지고 있고 색은 다른 아규먼트로 만들 수 있다.
     
  5. 이 방법은 다량의 규모가 작은 클래스들을 생성하는 방식이지만 프로세스에서 프로그램을 단순화하고 명백하게 설명한다.

'Dev... > 패턴자료' 카테고리의 다른 글

The Memento Pattern  (0) 2005.03.01
The Command Pattern  (0) 2005.03.01
The Iterator Pattern  (0) 2005.03.01
The Interpreter Pattern  (0) 2005.03.01
The Strategy Pattern  (0) 2005.03.01