The Memento Pattern

    어떤 객체의 내부 상태를 저장하고 그래서 후에 다시 저장 할 수 있게 한다 가정해 보자. 관념상으로는 저장할 수 있고 객체를 다시 생성하지 않고 재 저장할 수 있다. 이것이 Memento 패턴의 목적이다.

Motivation

객체들은 종종 public 메소드를 사용하여 그것들의 내부 상태의 단지 몇 개만 노출하지만, 나중에라도 그것을 다시 저장 해야 할 필요가 있을지도 모르기 때문에 어떤 객체의 전체적인 상태를 저장하고자 한다. 몇몇 경우에서 데이터를 저장하고 재 저장하는 public 인터페이스(그래픽 객체들의 위치를 그리는 것과 같은 것)로부터 충분한 정보를 얻을 수 있다. 다른 경우에는 색, 그림자, 각 과 다른 그래픽 객체들과의 관계 같은 정보는 쉽게 이용할 수 없다. 저장과 재저장의 정보에 대한 정렬은 취소 명령을 지원하는 시스템에서는 공통적인 것이다.

    하나의 객체를 설명하는 모든 정보가 public 변수로 이용할 수 있다면 외부로  저장하는 것은 어렵지가 않다. 그러나 이러한 데이터들을 공개하는 것은 외부 프로그램 코드에 의하여 쉽게 변경되는 시스템을 만들게 된다.

    Memento 패턴은 저장하고자 하는 객체의 상태를 권한 있는 접근을 갖게 하여 이 문제를 풀려고 시도한다. 다른 객체들은 그 객체에 더 제한된 접근을 가져 캡슐화를 유지한다. 이 패턴은 객체들에 대해 세가지 규칙을 정의한다:
  1. Originator는 우리가 저장하고자 하는 상태를 갖는 객체이다.
     
  2. Memento는 Originator의 상태를 저장하는 또 다른 객체이다.
     
  3. Caretaker는 상태를 저장하는 타이밍을 관리하고 Memento를 저장한다. 필요하다면 Originator의 상태를 재저장하는 Memento를 이용한다.

Implementation

어떤 객체의 모든 변수들을 공개적으로 이용할 수 있도록 만들지 않고 객체의 상태를 저장하는 것은 트릭이다. 자바에서는 이 권한 있는 접근은 약간만 알려줘 사용할 수 있고 드물게 접근 제한 방법을 사용한다. 자바 클래스에서 변수는 다음처럼 선언될 수 있다.
  1. Private
  2. Protected
  3. Public
  4. (Private protected)

    선언이 없는 변수들은 private protected로 취급된다. 다른 클래스들은 public  변수들로 접근할 수 있고, 상속된 클래스들은 protected 변수로 접근할 수 있다. 그러나 같은 모듈에서 또 다른 클래스들은 protected 나 private-protected 변수들로 접근될 수 있다. 이것은 Moment 객체들을 만들기 위해 사용할 수 있는 자바의 마지막 속성이다.  예를 들어 같은 모듈에서 선언된 클래스 A 와 B가 있다고 하자 :

public class A {	int x, y;	public Square() {}	x=5;	//initialize x}//-----------------------------------class B {	public B() {		A a = new A();	//create instance of A		System.out.println(a.x)	//has access to variables in A	}}
클래스 A는 private-protected 변수 x를 포함하고 있다. 같은 모듈에 있는 클래스 B에서 자동으로 x에 5를 초기화하는 A의 인스턴스를 생성했다. 클래스 B는 클래스 A에 있는 변수 x에 직접적인 접근을 할  수 있고 컴파일이나 런타임 에러 없이 프린트 할 수 있다. 이것이 Momento 를 생성하는데 사용할 정확한 특징이다.

Sample Code

사각형을 생성하는 드로잉 프로그램의 프로토타입을 고려해 보자. 그리고 마우스를 이용하여 사각형들을 선택하고 움직일 수 있도록 해보자. 이 프로그램은 세 개의  버튼을 포함한 투라를 가지고 있다: Rectangle, Undo, Clear:

       

Rectangle 버튼은 JToggleButton이다. 일단 사각형을 그리면 어떤 사각형이건 선택할 수 있다;

       

그리고 일단 선택되면, 사각형을 마우스를 이용하여 새로운 위치로 드래그할 수 있다.

Undo 버튼은 연산자들의 순서를 취소할 수 있다. 특히 그것은 사각형 이동을 취소하거나 생성을 취소할 수 있다.

    우리가 이 프로그램에서 반응할 필요가 있는 다섯 가지 액션이 있다.
  1. Rectangle 버튼이 클릭
  2. Unto 버튼 클릭
  3. Clear 버튼 클릭
  4. 마우스 클릭
  5. 마우스 드래그

    세 개의 버튼은 Command 객체로 생성되질 수 있고 마우스 클릭과 드래그는 역시 Command 로 다룰 수 있다. 이것은 Mediator 패턴을 사용하는 기회를 제공하고 사실 이 프로그램은 이와 같은 방법으로 작성된다.

    더구나 Mediator는  취소 액션 목록을 관리하는데 적격이다. 그러므로 Mediator는 위에서 설명했던 Caretaker 객체의 기능을 한다. 사실 그러한 프로그램에서 저장하고 취소하는 액션이 얼마든지 가능하기 때문에 Mediator는 이런 명령들이 나중에 취소하기 위해 저장될 수 있는 가상적인 하나의 장소이다.

    이 프로그램에서 우리는 저장과 취소 단지 두 가지 동작: 새로운 사각형을 생성하고 사각형의 위치를 변경만이 있다.  사각형의 인스턴스를 실제적으로 그리는 visRectangle 클래스 먼저 시작해 보자:

public class visRectangle {	int x, y, w, h;	Rectangle rect;	boolean selected;		public visRectangle(int xpt, int ypt) {		x = xpt;   y = ypt;		w = 40;    h = 30;		saveAsRect();	}		//-------------------------------------------	public void setSelected(boolean b) {		selected = b;	}		//-------------------------------------------	private void saveAsRect() {		rect = new Rectangle(x-w/2, y-h/2, w, h);	}		//-------------------------------------------	public void draw(Graphics g) {		g.drawRect(x, y, w, h);				if (selected) { //draw "handles"			g.fillRect(x+w/2, y-2, 4, 4);			g.fillRect(x-2, y+h/2, 4, 4);			g.fillRect(x+w/2, y+h-2, 4, 4);			g.fillRect(x+w-2, y+h/2, 4, 4);		}	}		//-------------------------------------------	public boolean contains(int x, int y) {		return rect.contains(x, y);	}		//-------------------------------------------	public void move(int xpt, int ypt) {		x = xpt; y = ypt;		saveAsRect();	}}
    사각형을 그리는 것은 꾀나 수월하다. 이제, 간단한 같은 visRectangle.java 파일에 있어 위치와 크기변수들에 접근할 수 있는 Memento 클래스를 보자.
class Memento {	visRectangle rect;	//saved fields- remember internal fields	//of the specified visual rectangle	int x, y, w, h;		public Memento(visRectangle r) {		rect = r;		x = rect.x;  y = rect.y;		w = rect.w;  h = rect.h;	}		//-------------------------------------------	public void restore() {		//restore the internal state of		//the specified rectangle		rect.x = x;  rect.y = y;		rect.h = h;  rect.w = w;	}}
우리가 Memento 클래스의 인스턴스를 생성했을 때 저장하고자 하는 visRectangle 인스턴스를 Memento 클래스에 전달한다. 그것은 크기와 위치 파라미터들을 복사하고 visRectangle 그 자체의 인스턴스의 사본을 저장한다. 후에 우리가 이러한 파라미터들을 다시 저장하려고 할 때 Memento 재저장하고 직접 할 수 있는 인스턴스를 알고 있다.

    나머지 동작은 우리가 취소 목록에 Integer로 드로잉의 목록의 전 단계를 저장하는  Mediator 클래스에서 이루어 진다.
public void createRect(int x, int y) {                                         	unpick();   //make sure no rectangle is selected                       	                                                                       	if(startRect) {                                                        		//if rect button is depressed                                  		Integer count = new Integer(drawings.size());                  		undoList.addElement(count);   //Save previous drawing list size		visRectangle v = new visRectangle(x, y);                       		drawings.addElement(v);       //add new element to list        		startRect = false;            //done with this rectangle       		rect.setSelected(false);      //unclick button                 		canvas.repaint();                                              	}                                                                      	else                                                                   		pickRect(x, y); //if not pressed look for rect to select       }                                                                              
    그리고 Memento에 옮기기 전에 사각형의 전 위치를 저장한다:
public void rememberPosition() {                           	if(rectSelected) {                                  		Memento m = new Memento(selectedRectangle);		undoList.addElement(m);                    	}                                                  }                                                          
    우리의 undo 메소드는 간단히 드로잉 리스트에서 Memento에 의해 축소하거나  Memento의 restore 메소드를 호출할지를  결정한다 :
public void undo() {                                                         	if(undoList.size()>0) {                                              		//get last element in undo list                              		Object obj = undoList.lastElement();                         		undoList.removeElement(obj);   //and remove it               		                                                             		//if this is an Integer, the last action was a new rectangle 		if (obj instanceof Integer) {                                			//remove last created rectangle                      			Object drawObj = drawings.lastElement();             			drawings.removeElement(drawObj);                     		}                                                            		                                                             		//if this is a Memento, the last action was a move           		if(obj instanceof Memento) {                                 			//get the Memento                                    			Memento m = (Memento)obj;                            			m.restore();     //and restore the old position      		}                                                            		                                                             		repaint();                                                   	}                                                                    }                                                                            

Consequences of the Memento Pattern

    Memento는  캡슐화가 가능한 언어에서 캡슐화를 유지하면서 어떤 객체의 상태를 저장하는 방법을 제공한다. 그러므로 유일한 Originator 클래스에서의 데이터는 private를 유지하는 효과적인 접근을 한다. 그것은 또 Memento 클래스에 정보를 저장 또는 재저장을 위임함으로써 Originator 클래스의 단순성을 보호한다.

    반면에 Memento 저장해야 하는  정보의 양이 클 수도 있다. 그래서 저장하는데 시간이 많이 걸릴 수 있다. 이것은 상태를 저장하기 위한 객체들의 수를 제한하는 전략을 설계할 수 있는 Caretaker 클래스(여기서는 Mediator)에서는 보다 효율적이다. 우리의 예제에서 그러한 제한들을 부과하지 않았다. 예측할 수 있는 방법으로 객체들이 바뀌는 경우에는 각각의 Memento는 객체의 상태의 단지 추가적인 변경만을 저장함으로써 얻을 수 있을지도 모른다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Memento Pattern  (0) 2005.03.01
The State 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
by 꿈꾸는자의 생각의파편들 2005.03.01 00:03

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. 이 방법은 다량의 규모가 작은 클래스들을 생성하는 방식이지만 프로세스에서 프로그램을 단순화하고 명백하게 설명한다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Command Pattern  (0) 2005.03.01
The Iterator Pattern  (0) 2005.03.01
The State 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
by 꿈꾸는자의 생각의파편들 2005.03.01 00:02

The Command Pattern

    Chain of Responsibility 패턴은 클래스들의 체인들을 따라서 요청들을 진행하지만, Command 패턴은 특별한 모듈로 하나의 요청을 진행한다. 그것은 하나의 객체 내부의 특정한 동작을 위한 하나의 요청을 둘러싸고 알려진 public 인터페이스를 준다. 그것이 수행하게 될 실제적인 동작에 대한 알고 있는 것 없이 요청들을 만들 수 있는 능력을 클라이언트에게 주게 한다. 그리고 어떤 방식으로든 클라이언트 프로그램에 영향을 주는 것 없이 동작을 변경할 수 있도록 한다.

Motivation

자바 사용자 인터페이스를 만들 때, 메뉴 항목들, 버튼들, 그리고 체크 박스와 같은 것들을 제공해야 하고, 프로그램이 무엇을 하는지를 사용자에게 알여 줘야 한다. 사용자가 이러한 컨트롤들 중의 하나를 선택했을 때, 프로그램은 하나의 ActionEvent를 받는다.
File|Open and File|Exit를 선택할 수 있는 메뉴항목과 버튼을 눌렀을 때 배경을 빨강색으로 바꿔주는 간단한 프로그램을 가정해 보자. 이 프로그램은 아래의 그림과 같다.

       

    이 프로그램은 mnuOpen 과 mnuExit를 MenuItem으로 가지는 File Menu로 구성되어 있다. 또 btnRed라 불리는 하나의 버튼을 포함한다. 어떤 것을 눌러도 우리는 다음 코드처럼 actionPerformed 메소드를 이용하여 이벤트를 잡아낼 수 있다.
	public void actionPerformed(ActionEvent e) {	Object obj = e.getSource();	if(obj == mnuOpen)		fileOpen();	//open file	if(obj == mnuExit)		exitClicked();	if(obj == btnRed)		redClicked();	//turn red}
    이 메소드가 부르는 3개의 private 메소드는 :
private void exitClicked() {	System.exit(1);}//-----------------------------------------private void fileOpen() {	FileDialog fDlg = new FileDialog(this, "Open a file", FileDialog.LOAD);	fDlg.show();}//-----------------------------------------private void redClicked() {	p.setBackground(Color.red);}
    몇개 안되는 메뉴 항목과 버튼들이 있을 때는 이러한 방식도 괜찮지만, 메뉴 항목이 수 십개가 되고 여러개의 버튼을 가지고 있을 때는 actionPerformed 코드는 꾀나 거대해지게 된다.

The Command Pattern

모든 객체가 자신의 명령을 직접적으로 받게 하는 한가지 방법은 Command 객체를 사용하는 것이다. Command 객체는 항상 액션이 객체내에서 발생했을 때 호출되는 Execute() 메소드를 가지고 있다.   
가장 간단하게, Command 객체는 적어도 다음의 인터페이스를 구현한다.
public interface Command {	public void Execute();}
이 인터페이스를 사용하는 목적은 actionPerformed 메소드를 다음과 같이 줄이기 위해서이다.
public void actionPerformed(ActionEvent e) {	Command cmd = (Command)e.getSource();	cmd.Execute();}
그러면 우리는 요구되는 엑션을 수행하는 각각의 객체에 대한 Execute 메소드를 제공할 수 있어, 그 것이 속해 있는 객체내에서 무엇을 할 것인가에 대한 정보를 유지할 수 있다.

    Command 패턴의 중요한 목적은 프로그램을 유지하고 사용자 인터페이스 객체들을 완전히 분리하는 것이고 다른 객체들의 작업에 대해 알 필요가 없다. 사용자 인터페이스는 명령을 받고 Command 객체에게 어떤 종류의 의무든지 알려줄 수가 있다. 사용자 인터페이스는 어떤일을 해야할 지 알려고 하지 않아도 되고 알 필요도 없다.

    Command 객체는 또 리소들이 바로 사용되지 않을 때 명령을 수행하는 프로그램에게 알려 줄 필요가 있을 때 사용되어 질 수 있다. 이러한 경우에서는 나중에 실행되는  queuing 명령들이다.
마지막으로, 취소 요구들을 지원할 수 있기 위해서 연산자들을 기억하는 Command 객체에도 사용되어 질 수 있다.

Building Command Objects

    이와 같은 프로그램을 위한 Command 객체들을 만드는 거에 관한 여러가지 방법이 있고 각각의 방법은 몇가지 이점을 갖는다. 우리는 가장 간단한 방법부터 시작할 것이다 : MenuItem 과 Button 클래스에서 파생 받아 새로운 클래스를 만들고 각각 Command 인터페이스를 구현한다.  
class btnRedCommand extends Button implements Command {	public btnRedCommand(String caption) {		super(caption);	//initialize the button	}	public void Execute() {		p.setBackground(Color.red);	}	//----------------------------------------	class fileExitCommand extends MenuItem implements Command {	public fileExitCommand(String caption) {		super(caption);	//initialize the Menu	}	public void Execute() {		System.exit(0);	}
    이 방법은 확실히 actionPerformed 메소드에서 만들어진 것을 간단하게 호출할 수 있지만, 실행하고자 하는 각각의 동작을 위하여 새로운 크래스를 만들고 인스턴스화 하는 것을 요구한다.
	mnuOpen.addActionListener(new fileOpen());	mnuExit.addActionListener(new fileExit());	btnRed.addActionListener(new btnRed());
    우리는 이러한 클래스들에 필요한 파라미터드을 전달하는 대부분의 문제를 그것들의 내부 클래스를 만들어 우회할 수 있다. 이것은 직접 이용할 수 있는 Panel 과 Frame 과 같은 객체들을 만든다.

    그러나, 내부의 클래스들은 명령들이 급증함에 따라 좋은 생각은 아니다. 왜냐면, 어떤 다른 사용자 인터페이스 컴포넌트에 접근하는 그것들 중의 어떤것도 메인 클래스 내부에서 유지해야 하기 때문이다. 이것이 혼란스러운 작은 내부 클래스의 증가로 메인 클래스에 대한 코드는 복잡해 진다.

    물론, 이러한 클래스들에게 필요한 파라미터들을 전달하는 것을 꺼려하지 않는다면 그것들은 독립적으로 될 수 있다. 여기서 우리는 파라미터로 Frame 객체와 Panel 객체를 전달하였다.
	mnuOpen = new fileOpenCommand("Open...", this);	mnuFile.add(mnuOpen);	mnuExit = new fileExitCommand("Exit");	mnuFile.add(mnuExit);	p = new Panel();	btnRed = new btnRedCommand("Red", p);	p.add(btnRed);
두번째 경우에서 우리의 메뉴와 버튼 command 클래스들은 메인 클래스 외부에 존재할 수 있고, 원한다면 파일을 나누어 저장할 수 도 있다.

The Command Pattern in Java

그러나 여전히 이 패턴을 접근하는 두어가지 방법이 있다. 만약 모든 컨트롤을 그 자신의 actionListener 클래스에 주었다면, 각각의 command 객체들을 효과적으로 생성한 것이다. 그리고, 사실, 이것이 자바1.1 이벤트 모델의 설계자들에게 있어서는 실제적이다.
    이 방법을 구현하기 위해 우리는 ActionListener 클래스를 구현한 작은 클래스들을 생성한다.
class btnRed implements ActionListener {	public void actionPerformed(ActionEvent e) {		p.setBackground(Color.red);	}}	//------------------------------------------class fileExit implements ActionListener {	public void actionPerformed(ActionEvent e) {		System.exit(0);	}}
그리고 일반적으로 리스너로써 그것들을 등록한다.
	mnuOpen.addActionListener(new fileOpen());	mnuExit.addActionListener(new fileExit());	btnRed.addActionListener(new btnRed());

Consequences of the Command Pattern

Command 패턴의 주요 단점은 메인 클래스를 어지럽게 하는 작은 클래스들의 급증이다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The State Pattern  (0) 2005.03.01
The Iterator Pattern  (0) 2005.03.01
The Command Pattern  (0) 2005.03.01
The State Pattern  (0) 2005.03.01
The Iterator Pattern  (0) 2005.03.01
The Interpreter Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:02

The Iterator Pattern

    Iterator 는 디자인 패턴에서 가장 간단하고 가장 빈번하게 사용되는 패턴들 중의 하나이다. Iterator 패턴은 데이터의 내부적인 표현을 자세하게 아는 것 없이 표준적인 인터페이스를 이용한 데이터의 리스트나 컬렉션을 통하여 이동하는 것을 허용한다. 게다가 어떤 특별한 프로세싱과 데이터 컬렉션의 특정한 원소를 반환을 하는 특별한 Iterator를 정의할 수 있다.

Motivation

    Iterator는  어떻게 Iterator 가 데이터를 이동시키는지를 드러내지 않고 데이터 원소들의 집합을 통하여 이동하는 정의된 Iterator는 인터페이스(interface)이기 때문에 반환되는 데이터를 위한 편리한 어떤 방법이든 구현할 수 있다.
public interface Iterator {	public Object First();	public Object Next();	public boolean isDone();	public Object CurrentItem();}
여기서 리스트의 최상위로 이동할 수 있고 리스트를 통하여 이동하고, 원소들이 더 있는지를 알아내고, 현재 리스트의 항목을 찾을 수 있다. 이 인터페이스는 구현하기가 쉽고 확실한 장점들을 가지고 있지만, 자바에서 Iterator의 선택은 Enumeration 타입으로 만들어져 있다 (자바 초창기에는 그랬지만, 현재는 Iterator 가 인터페이스로 존재한다).
public interface Enumeration {	public boolean hasMoreElements();	public Object nextElement();}
    리스트의 상위로 이동하는 메소드를 가지고 있지 않은 것이 첫째로 제한적인 것처럼 보여지지만 자바에서는 그것은 심각한 문제가 아니다. 왜냐면 리스트를 통하여 이동하고자 할 때마다 Enumeration의 새로운 인스턴스를 얻는 것이 통상적이기 때문이다. 자바 Enumeration의 한가지 단점은 자바 언어의 강한 형 변환이다. 이것은 hasMoreELements() 메소드가 반환된 Object 타입을 실제적인 타입으로 캐스팅하는 귀찮은 요구 없이 컬렉션에서 데이터의 실제적인 타입의 객체를 반환하는 것을 방해한다. 그러므로 Iterator 나 Enumeration 인터페이스는 다형성을 의도하는 것이다.

Enumerations in Java

Enumeration 타입은 Vector 와 Hashtable 클래스들로 만드어져 있다. 두 클래스는 클래스의 데이터의 Enumeration을 반환하는 elements 메소드를 포함하고 있다.

public Enumeration elements();

    이 elements() 메소드는 실제로 Enumeration 클래스의 인스턴스를 생성하는 Factory 메소드의 일종이다.

    그리고 나서 다음 코드를 가지고 리스트를 통해 이동시킨다.
Enumeration e = vector.elements();while (e.hasMoreElements()) {	String name = (String)e.nextElement();	System.out.println(name);}
    추가적으로 HashTable은 테이블에서 각 원소에 키들의 enumeration을 반환하는 keys 메소드를 가지고 있다.

public Enumeration keys ();

    이것이 자바에서 Enumeration을 구현하기 위한 발탁된 스타일이고 같은 데이터의 enumeration들을 동시에 동적으로 얼마든지 가질 수 있는 이점이 있다.

Filtered Iterators

하나의 컬렉션을 통하여 정확히 정의된 이동 메소드를 갖는 것은 도움이 되고, 반환되기 전의 데이터에 대한 몇 가지 계산을 수행하여 걸러진 Enumerations 을 정의할 수 있다. 예를 들어 특별한 방법으로 정렬된 데이터를 반환할 수 있거나  특정 영역에 매치되는 단지 몇 개의 객체들을 반환할 수 있다. 그래서, 걸려진 enumerations 들을 위한 매우 유사한 인터페이스들을 많이 같이 가지고 있는것 보다 같은 메소드를 갖는 이러한 enumerations 의 각각의 것을 가지고서  enumeration 의 각 타입을 반환하는 메소들 간단히 제공한다.

Sample Code

Interpreter 단원에서 설명했던 수영선수들, 클럽들, 시간들의 목록을 재사용하고 KidData 클래스에  몇 가지 기능을 추가해 보자. 
public class KidData {	Vector kids;	//------------------------------------------   	public KidData(String filename) {		kids = new Vector();		InputFile f = new InputFile(filename);		String s = f.readLine();				while(s != null) {			if(s.trim().length() > 0) {				Kid k = new Kid(s);				kids.addElement(k);			}					s = f.readLine();		}	}		//--------------------------------	public Enumeration elements() {		//return an enumeration of the kids		return kids.elements();	}	
    컬렉션에서 모든 Kids의 enumeration을 얻기 위해, 우리는 간단히 Vector 그 자체의 enumeration을 반환한다.

The Filtered Enumeration

    그러나 우리가 어떤 클럽에 속해 있는 그러한 아이들에 대해서만 enumerate 하기를 원했었다고 가정해 보자. 이것은 KidData 클래스에서 데이터에 접근하는 특별한 Enumeration 클래스를 필요로 한다. 그런 접근을 하도록 정의된 elements() 메소드 때문에 이것은 간단하다. 그리고 나서 우리는 특정의 클럽에 속한 아이들을 반환하는 Enumeration 을 작성이 필요하다.
public class kidClub implements Enumeration {	String clubMask;     //name of club	Kid kid;             //next kid to return	Enumeration ke;      //gets all kids	KidData kdata;       //class containing kids		//----------------------------------------		public kidClub(KidData kd, String club) {		clubMask = club;     //save the club		kdata = kd;          //copy the class		kid = null;          //default		ke = kdata.elements();  //get Enumerator	}		//----------------------------------------		public boolean hasMoreElements() {		//return true if there are any more kids 		//belonging to the specified club		boolean found = false;		while(ke.hasMoreElements() && ! found) {			kid = (Kid)ke.nextElement();			found = kid.getClub().equals(clubMask);		}		if(! found) 		kid = null;    //set to null if none left		return found;	}	//----------------------------------------	public Object nextElement() {		if(kid != null)			return kid;		else			//throw exception if access past end			throw new NoSuchElementException();	}}
    모든 작업은 생성자에서 정해진 클럽에 속한 또 다른 아이에 대한 컬렉션을 통하여 읽은 hasMoreElements() 메소드에서 마쳐지고, kid 변수에 kid를 저장하거나 더 이상 아이들이 없으면 예외를 발생시킨다. 정상적인 상황에서 이 예외는 절대로 발생되지 않는다.

    마지막으로 우리는 KidData에 새로게 걸러진 Enumeration을 반환하는 메소드 추가가 필요하다.
	public Enumeration kidsInClub(String club) {		return new kidClub(this, club);                                       	}
이 간단한 메소드는 클럽 이니셜과 관련된 Enumeration 클래스 kidClub에 KidClub의 인스턴스를 전달한다. 간단한 프로그램은 아래와 같고 좌측에 모든 아이들에 대하여 나타내고 우측에 속해있는 클럽들을 나타낸다.

       

Consequences of the Iterator Pattern

  1. 데이터 수정. iterators 에 대한 가장 의미 있는 질문은 데이터가 변경되는 동안 데이터를 통하여 iterating의 질문이다. 코드가 광범위해지고 오직 가끔 다음 원소로 이동한다면, 그것을 통하여 이동하는 동안에 기본적인 컬렉션에서 어떤 원소가 추가되거나 삭제될 수 있다. 또, 그것은 그 컬렉션을 변경할 수 있는 또 다른 쓰레드가 가능하다.
     
  2. 권한 있는 접근. Enumeration 클래스들은  원래의 컨테이너 클래스의 기본적인 자료 구조에 권한 있는 접근의 몇몇 정렬을 필요로 한다. 그래서 그것들은 데이터를 통해서 이동할 수 있다. 만약 데이터가 벡터나 해쉬테이블에서 정렬되어 있다면 이동하기가 더 쉽지만, 클래스에 포함된 다른 컬렉션 구조가 있다면 get 연산자를 통하여 이용할 수 있는 구조를 만들어 줘야 한다. 대신 견제하는 클래스로부터 상속 받은  Iterator 클래스를 만들 수 있고 데이터를 직접 접근할 수 있다.
     
  3. 외부 대 내부 Iterators. 디자인 패턴 교재에서는 Iterators 의 두 가지 타입을 설명하고 있다 : 외부 와 내부. 우리는 외부 Iterator 만 설명했다. 내부의 Iterator 는 전체적인 컬렉션을 통하여 이동되는 방법이고 사용자의 특별한 요청 없이 각 원소에 대해 직접적인 연산을 수행한다. 이러한 것들은 자바에서 공통적인 것은 아니지만 데이터의 값을 0 과 1사이의 값으로 컬렉션을 정규화하는 메소드를 상상할 수 있다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The State Pattern  (0) 2005.03.01
The Command Pattern  (0) 2005.03.01
The Iterator Pattern  (0) 2005.03.01
The State Pattern  (0) 2005.03.01
The Command Pattern  (0) 2005.03.01
The Interpreter Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:02

The Interpreter Pattern

    몇몇 프로그램들은 그것들이 수행할 수 있는 연산자들을 설명하는 언어를  갖는 것으로부터 이점을 얻는다. Interpreter 패턴은 일반적으로 언어에 대한 문법 정의를 묘사하고 언어에서 문장들을 해석하는 문법 사용을 묘사한다.

Motivation

프로그램이  다른 것들을 나타내지만 몇 가지는 유사한 경우가 있을 때 Interpreter 패턴은 이러한 경우들을 묘사하는 간단한 언어를 이용할 수 있는 이점이 있고, 언어를 해석하는 프로그램을 갖는다.

    문제중의 하나는 우리가 다루어야 할 것이 언어가 도움을 줄 수 있을 때를 어떻게 인식하는가 이다. 매크로 언어의 리코더는 단순히 후에 재생되기 위한 메뉴와 키스트로크 연산자들을 기록한다.

Applicability

Interpreter 가 어디에서 도움을 줄 수 있는지를 인식하는 것은 어려운 문제이고, 프로그래머들은 형식적인 언어/컴파일러 연습 없이 종종 이러한 접근을 간과한다. Interpreter 패턴이 사용되는 경우가 많지는 않지만, 언어들이 이용할 수 있는 두 가지의 일반적인 경우가 있다.
  1. 프로그램이 대수적인 문자열을 해석해야만 할 때. 이 경우는 명확한 경우이다. 프로그램은 사용자가 입력한 어떤 부류의 식이 있는 계산에 기인한 연산자들을 수행하도록 요청된다. 이것은 종종 수리적인 그래픽 프로그램에서 발생한다.
     
  2. 프로그램이 다양한 종류의 결과를 만들어 내야 할 때. 이 경우는 약간 명확하지 않지만, 좀더 유용하다. 정렬되지 않는 데이터의 열들을 나타내고 다양하게 정렬할 수 있는 프로그램을 고려해 보자. 이러한 프로그램은 종종 보고서 생성기로 참조되어지고, 원래 데이터는 관계 형 데이터베이스에 저장되어 있다. 보고서 작성 프로그램에 대한 사용자 인터페이스는 일반적으로 SQL 보다 훨씬 간단하다. 사실 몇 가지 경우에서 간단한 보고서 언어는 보고서 프로그램으로 해석되어 질 수 있고 SQL로 변환될 수 있다.

Sample Code

테이블에서 5개의 데이터 열을  연산하고 이러한 데이터로 다양한 보고서들을 반환하는 보고서 생성기를 단순화하여 고려해 보자. 우리가 어떤 수영 대회에서 다음의 정렬된 결과를 가지고 있다고 해보자. 
 
First NameLast NameAgeClubTime
AmandaMcCarthy12WCA29.28
JamieFalco12HNHS29.80
MeaghanO'Donnell12EDST30.00
GreerGibbs12CDEV30.04
RhiannonJeffrey11WYW30.04
SophieConnolly12WAC30.05
Dana Helyer12ARAC30.18
51명 수영선수들의 결과를 얻었다면 우리는 클럽, 성이나 나이에 따라 결과를 정렬하는 것이 편리할지도 모른다는 것을 인식할 것이다.

    우리는 정렬의 재귀적이지 않는 문법을 정의할 것이다.

Print lname frname club time sortby club thenby time

    이 예제에서는 아래의 단어들처럼 3개의 동사를 정의하였다.

Print
Sortby
Thenby

    그리고 5개의 열 이름이 나열되었다.
Frname
Lname
Age
Club
Time

    편리성을 위하여 우리는 언어는 사건에 둔감하다고 가정하자. 또 이 언어의 간단한 문법은 구두점이 없다는 것이다. 그리고 결과적으로 다음처럼 된다.

    Print var[var] [sortby var [thenby var]]

마지막으로 유일한 주 동사가 있고 반면에 각각의 문장이 선언되어진다. 이 문법에서는 문장 할당이나 계산 능력이 없다.

Interpreting the Language

인터프리팅 언어는 3개의 단계로 이루어진다.
  1. 토큰(token)으로 언어의 기호를 해석한다.
  2. 토큰들을 액션(action)으로 분해한다.
  3. 액션을 실행한다.

우리는 StringTokenizer를 가지고 각각의 문장을 읽고 각각의 단어에 대해 어떤 수로 대체해서 언어를 토큰으로 해석한다. 일반적으로 파서들은 분해된 토큰을 하나의 스택에 넣는다. 우리는 Stack 클래스를 벡터를 사용하여 구현하였다. Stack 클래스는 스택 내용을 조사하고 쉽게 다루기 위해 push, pop, top 그리고 nextTop 메소드를 가지고 있다.

    파싱후에 우리의 스택은 아래와 같이 보여질 수 있다:

TypeToken  
VarTime← top of Stack
VerbThenby
varClub
VerbSortby
VarTime
VarFrname
VerbLname

     그러나, 우리는 "verb" thenby 는 다른 것보다 실제적인 의미가 없다는 것을  알 수 있다. 우리의 초기 스택은 다음처럼 보여진다.

Time
Club
Sortby
Time
Club
Frname
Lname
Print

Objects Used in Parsing

실제적으로 우리는 스택에 수리적인 토큰은 넣지 않지만, ParseObject 타입과 값을 갖는다 
public class ParseObject {	public static final int VERB=1000, VAR = 1010, MULTVAR = 1020;	protected int value;	protected int type;		public int getValue() {return value;}	public int getType() {return type;}}
이러한 객체들은 VERB 나 VAR 타입을 가질 수 있다. 그리고 나서 우리는 이 객체를 ParseVerb 와 ParseVar 객체로 확장한다.

    여기에 간단한 계층구조가 있다:

       

    파싱 프로세서는 StringTokenizer 와 파서 객체들을 이용하여 아래처럼 코드를 작성할 수 있다.
public Parser(String line) {	stk = new Stack();	actionList = new Vector();		StringTokenizer tok = new StringTokenizer(line);		while(tok.hasMoreElements()) {		ParseObject token = tokenize(tok.nextToken());		if(token != null)			stk.push(token);	}}           //----------------------------------------private ParseObject tokenize(String s) {	ParseObject obj = getVerb(s);	if (obj == null)		obj = getVar(s);	return obj;}//----------------------------------------private ParseVerb getVerb(String s) {	ParseVerb v;	v = new ParseVerb(s);	if (v.isLegal()) 		return v.getVerb(s);	else		return null;}//----------------------------------------private ParseVar getVar(String s) {	ParseVar v;	v = new ParseVar(s);	if (v.isLegal()) 		return v;	else		return null;}   
ParseVerb 와 ParseVar 클래스는 단어가 인식되었다면 'true'가 되어 객체들을 반환한다.
public class ParseVerb extends ParseObject {	static public final int PRINT=100, SORTBY=110, THENBY=120;	protected Vector args;	   	public ParseVerb(String s) {		args = new Vector();		s = s.toLowerCase();		value = -1;		type = VERB;		if (s.equals("print")) value = PRINT;		if (s.equals("sortby")) value = SORTBY;	}

Reducing the Parsed Stack

    스택에 대한 토큰은 다음과 같은 형식을 갖는다.

Var
Var
Verb
Var
Var
Var
Var
Verb


    우리는 아규먼트들이 동사 객체를 포함할 때 까지 MultVar 클래스로 연속적인 변수들을 포개어서 동시에 토큰에 대한 스택을 줄일 수 있다.



스택이 동사로 축소되었을 때, 이 동사와 그것의 아규먼트는 액션 리스트에 있다; 액션이 실행되었을 때 스택이 비게 된다.

    이 전체적인 프로세스는 사용자 인터페이스에서 Go 버튼을 눌렀을 때 실행하는 Command 객체가 있는 Parse 클래스를 생성하여 수행할 수 있다 :
	public void actionPerformed(ActionEvent e) {		Parser p =  new Parser(tx.getText());		p.setData(kdata, ptable);		p.Execute();	}
파서 자체가 위에서 본 것처럼 토큰들을 축소한다. 그것은 스택에서 여러 가지 토큰의 쌍을 체크하고, 각각 다른 다섯 가지의 경우에 대해 각 쌍을 하나의 쌍으로 축소한다.
	//executes parse of command line	public void Execute() {		while(stk.hasMoreElements()) {			if(topStack(ParseObject.VAR, ParseObject.VAR)) {				//reduce (Var Var) to Multvar				ParseVar v = (ParseVar)stk.pop();				ParseVar v1 = (ParseVar)stk.pop();				MultVar mv = new MultVar(v1, v);				stk.push(mv);			}				//reduce MULTVAR VAR to MULTVAR			if(topStack(ParseObject.MULTVAR, ParseObject.VAR)) {				MultVar mv =  new MultVar();				MultVar mvo = (MultVar)stk.pop();				ParseVar v = (ParseVar)stk.pop();				mv.add(v);				Vector mvec = mvo.getVector();				for (int i = 0; i< mvec.size(); i++)				mv.add((ParseVar)mvec.elementAt(i));				stk.push(mv);			}						//reduce (Multvar Var) to Multvar			if(topStack(ParseObject.VAR, ParseObject.MULTVAR)) {				ParseVar v = (ParseVar)stk.pop();				MultVar mv = (MultVar)stk.pop();				mv.add(v);				stk.push(mv);			}						//reduce Verb Var to Verb containing vars			if (topStack(ParseObject.VAR, ParseObject.VERB)) {				addArgsToVerb();			}						//reduce Verb MultVar to Verb containing vars			if (topStack(ParseObject.MULTVAR, ParseObject.VERB)) {				addArgsToVerb();			}						//move top verb to action list			if(stk.top().getType() == ParseObject.VERB) {				actionList.addElement(stk.pop());			}					}//while				//now execute the verbs		for (int i =0; i< actionList.size() ; i++) {			Verb v = (Verb)actionList.elementAt(i);			v.setData(data, ptable);			v.Execute();		}	}
우리는 또한 Print 와 Sort Verb 클래스들을 Command 객체들을 만들어 열거된 액션 목록을 하나씩 실행한다.

    최종적인 프로그램은 아래 그림과 같다.

       

Consequences of the Interpreter Pattern

    프로그램에서 인터프리터를 소개할 때마다 사용자가 언어에서 명령들을 입력하는 프로그램을 위해 간단한 방법을 제공할 필요가 있다. 앞에서 언급했던 것처럼 Macro를 기록하는 방법이 있고 위의 프로그램에서처럼 텍스트 필드를 만들어 편집할 수 있게 하는 방법이 있다.

    그러나 언어와 그에 동반된 문법을 수용하는 것은 또한 잘못 입력된 항목이나 문법적으로 잘못 놓여진 원소에 대한 꾀나 광대한 에러 체크를 요하게 된다. 사용자가 이런 에러들을 인식하기 위한 좀더 효율적인 방법은 설계하고 구현하기가 쉽지 않다.

    위의 인터프리터 예제에서는 유일한 에러 핸들링은 인식되지 않는 키워드는  ParseObject로 변환되지 않는다는 것이고 스택에 추가하지 않는다는 것이다. 그러므로, 아무일도 발생하지 않는데 결과 스택 순서가 성공적으로 해석될 수 없기 때문이다.

    또, 라디오 버튼이나 명령버튼 과 리스트 박스의 사용자 인터페이스에서 자동으로 언어가 생성되는 것을 고려해야 한다. 반면에 그러한 인터페이스를 사용하여 언어에 대한 필요성이 없어지는 것처럼 보일 수 있지만 같은 시퀀스의 요구나 계산은 여전히 적용된다. 우리가 순차적인 연산의 순서를 정하는 방법을 가지고 있어야만 할 때 그 언어가 사용자 인터페이스로부터 생성된다 할 지라도 언어는 그렇게 하는 좋은 방법이다.

    인터프리터 패턴은 일단 일반적인 파싱과 리덕션 도구들을 만들었으면  쉽게 확장하거나 문법을 수정할 수 있는 이점을 가지고 있다.일단 기초가 구성되면 쉽게 새로운 동사나 변수를 추가할 수 도 있다. 

    Parse 클래스에서 보았던 해석 구조에서 단지 6가지 경우만 고려했고 문장 자체가 간단했다. 우리가 좀더 확장을 할 때 디자인 패턴은 그것들 각각에 대한 클래스를 생성할 것을 제안했다. 이것은 다시 언어가 쉽게 확장될 수 있게 하지만 유사한 작은 클래스들의 양을 증가시키는 단점을 가지고 있다.

    마지막으로 문법이 더욱 복잡해져 감에 따라 프로그램을 유지하는 것이 어려워 질 수 있다.

    반면에 인터프리터들은 일반적인 프로그래밍 문제들을 푸는데 있어서 공통적인 것은 아니다. 다음 장에 나오는 Iterator 패턴이 사용할 수 있는 가장 공통적인 것이다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Iterator Pattern  (0) 2005.03.01
The Strategy Pattern  (0) 2005.03.01
The Interpreter Pattern  (0) 2005.03.01
The Strategy Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:01

The Strategy Pattern

    Strategy 패턴은 윤곽은 State 패턴과 많이 비슷하지만 의도하는 목적이 약간 다르다. Strategy 패턴은 Context라 불리는 드라이버 클래스에서 캡슐화된 다수의 관련된 알고리즘들로 구성된다. 클라이언트 프로그램은 다른 알고리즘을 선택하거나 어떤 경우에는 Context가 최상의 알고리즘을 선택할 수 있다. State 패턴과 같이 어떤 조건문의 덩어리 없이 알고리즘들을 쉽게 바꿀 수 있도록 하는 것이 의도이다. 

Motivation

    특별한 서비스나 기능을 요구하는 프로그램과 기능을 수행하는 여러 가지 방법을 가지고 있는 것은 Strategy 패턴의 후보이다. 프로그램들은 컴퓨터 효율이나 사용자 선택을 기준으로 알고리즘을 선택한다.

    우리가 여러 가지 다른 방법으로 같은 일을 하려고 하는 프로그램에서 다수의 경우가 있다.
  • 다른 포멧으로 파일들을 저장한다.
  • 다른 알고리즘을 사용하여 파일들을 압축한다
  • 다른 압축 구조를 이용하여 비디오 데이터를 캡쳐한다.
  • 텍스트 데이터를 나타내기 위한 다른 line-breaking 전략을 이용한다.
  • 같은 데이터를 다른 포멧으로 그린다 : 선 그래프, 막대 그래프 나 파이 차트
    각각의 경우에 우리는 사용할 수 있는 전략들의 드라이버 모듈(Context)를 알려주는 클라이언트 프로그램을 상상할 수 있다.

Sampe Code

선그래프나 막대그래프로 데이터를 표현할 수 있는 간단한 그래픽 프로그램을 고려해 보자. 우리는 추상적인 PlotStrategy 클래스 부터 시작할 것이고 두 개의 플랏팅 클래스들을 파생시킬 것이다.

       

    각각의 플랏이 그들 자신의 프레임에서 보여지기 때문에 base PlotStrategy 클래스는 JFrame 으로부터 파생될 것이다 :
public abstract class PlotStrategy extends JFrame {	protected float[] x, y;	protected float minX, minY, maxX, maxY;	protected int width, height;	protected Color color;		public PlotStrategy(String title) {		super(title);		width = 300;		height =200;		color = Color.black;		addWindowListener(new WindAp(this));	}		//--------------------------------------	public abstract void plot(float xp[], float yp[]);		//--------------------------------------	public void setSize(Dimension sz) {		width = sz.width;		height = sz.height;	}		//--------------------------------------	public void setPenColor(Color c) {		color = c;	}
    중요한 부분은 파생된 클래스들이 plot이라 불리는 메소드를 구현해야만 한다는 것이다. 이 클래스들 각각은 적당한 어떤 종류의 플랏이라도 가능하다.

The Context

    Context 클래스는 strategy가 어디에서 호출될 것이가를 결정하는 교통 경찰이다. 결정은 일반적으로 클라이언트 프로그램의 요구에 기인하고 결정할 필요가 있는 Context는 하나의 구체적인 전략이나 다른 을 참조하는 변수를 정한다.
public class Context {	//this object selects one of the strategies	//to be used for plotting	private PlotStrategy plotStrategy;	float x[], y[];		//---------------------------------	public Context() { setLinePlot(); }		public void setBarPlot() {plotStrategy = new BarPlotStrategy(); }		public void setLinePlot() { plotStrategy = new LinePlotStrategy(); }		//---------------------------------	public void plot() {		plotStrategy.plot(x, y);	}		//---------------------------------	public void setPenColor(Color c) {		plotStrategy.setPenColor(c);	}		//---------------------------------	public void readData(String filename) {		StringTokenizer tok;		InputFile f = new InputFile(filename);		Vector xv = new Vector();		Vector yv = new Vector();		String s ="";		//read data into 2 Vectors		while(s != null) {			s =f.readLine();   //read a line at a time			if(s != null) {				tok = new StringTokenizer(s);  				xv.addElement(tok.nextToken());   //x data				yv.addElement(tok.nextToken());   //y data			}		}				f.close();		//copy data into two float arrays		x = new float[xv.size()];		y = new float[yv.size()];				for (int i=0; i< xv.size(); i++) {			x[i] = new Float((String)xv.elementAt(i)).floatValue();			y[i] = new Float((String)yv.elementAt(i)).floatValue();		}	}}
    Context 클래스는 또한 데이터를 핸들링 하는데 또 다른 책임이 있다. 파일이나 데이터베이스로부터 데이터를 얻거나 Context가 생성될 때 전달한다. 데이터의 양에 의존하여 plot strategies 에 전달되거나 Context는 plot strategies로 그 자체의 인스턴스를 전달할 수 있고 데이터를 가져오는 public 메소드를 제공한다.

The Program Commands

    이 간단한 프로그램은 단지 두 개의 plot을 호출할 수 있는 두 개의 버튼을 가진 패널이다:

       

    각각의 버튼들은 정확한 strategy를 정하는 명령 객체이고 Context의 plot 루틴을 호출한다. 예를 들어 아래는 Line graph button 클래스이다 :
public class JGraphButton extends JButton implements Command {	Context context;		public JGraphButton(ActionListener act, Context ctx) {		super("Line graph");		addActionListener(act);		context  = ctx;	}		//-------------------------------	public void Execute() {		context.setPenColor(Color.red); //set color of plot		context.setLinePlot();        //set kind of plot		context.readData("data.txt"); //read the data		context.plot();               //plot the data	}}

The Line and Bar Graph Strategies

두 개의 strategy 클래스들은 많이 비슷하다 : plotting을 위한 윈도우 크기를 정하고 plot 메소드를 호출한다.
public class LinePlotStrategy extends PlotStrategy {	LinePlotPanel lp;		//--------------------------------------	public LinePlotStrategy() {		super("Line plot");		lp = new LinePlotPanel();		getContentPane().add(lp);	}		//--------------------------------------		public void plot(float[] xp, float[] yp) {		x = xp;  y = yp;        //copy in data		findBounds();           //sets maxes and mins		setSize(width, height);		setVisible(true);		setBackground(Color.white);		lp.setBounds(minX, minY, maxX, maxY);		lp.plot(xp, yp, color); //set up plot data		repaint();              //call paint to plot	}}

Drawing Plots in Java

자바 GUI는 event-dirven이기 때문에, 실제적으로 plot 명령 이벤트에 직접 반응하여 화면에 선들을 그리는 루틴을 직접 작성하지 않는다. 대신 이벤트가 호출될 때 plotting을 수행하는 paint 이벤트를 갖는 패널을 제공한다. repaint 메소드는 직접 paint 메소드를 호출할 수 있다.
    우리는 JPanel을 토대로 PlotPanel을 생성하고 그것으로부터 실제적인 선과 막대를 위한 두 개의 클래스를 상속받는다:

       

 base PlotPanel 클래스는 윈도우 맞게 데이터를 스케일링하기 위한 공통적인 코드를 포함한다.
public class PlotPanel extends JPanel {	float xfactor, yfactor;	int xpmin, ypmin, xpmax, ypmax;	float minX, maxX, minY, maxY;	float x[], y[];	Color color;		//--------------------------------------------	public void setBounds(float minx, float miny, float maxx, float maxy) {		minX=minx;		maxX= maxx;		minY=miny;		maxY = maxy;	}                                           		//--------------------------------------------	public void plot(float[] xp, float[] yp, Color c) {		x = xp;      //copy in the arrays		y = yp;		color = c;   //and color				//compute bounds and sclaing factors		int w = getWidth() - getInsets().left - getInsets().right;		int h = getHeight() - getInsets().top - getInsets().bottom;		  		xfactor = (0.9f * w) / (maxX - minX);		yfactor = (0.9f * h)/ (maxY - minY);				xpmin = (int)(0.05f * w);		ypmin = (int)(0.05f * h);		xpmax = w - xpmin;		ypmax = h - ypmin;		repaint();      //this causes the actual plot	}		//--------------------------------------	protected int calcx(float xp) {		return (int)((xp-minX) * xfactor + xpmin);	}		//--------------------------------------	protected int calcy(float yp) {		int ypnt = (int)((yp-minY) * yfactor);		return ypmax - ypnt;	}}
    두 개의 파생받은 클래스는 간단하게 두 종류에 대한 paint 메소드를 구현한다. 다음은 선 그림에 대한 코드이다.
public class LinePlotPanel extends PlotPanel {	//--------------------------------------	public void paint(Graphics g) {		int xp = calcx(x[0]);      //get first point		int yp = calcy(y[0]);		g.setColor(Color.white);   //flood background		g.fillRect(0,0,getWidth(), getHeight());		g.setColor(Color.black);				//draw bounding rectangle		g.drawRect(xpmin, ypmin, xpmax, ypmax);		g.setColor(color);			//draw line graph		for(int i=1; i< x.length; i++) {			int xp1 = calcx(x[i]);			int yp1 = calcy(y[i]);			g.drawLine(xp, yp, xp1, yp1);			xp = xp1;			yp = yp1;		}	}}
    아래의 그림은 최종적인 두 개의 plot이다 :

Consequences of the Strategy Pattern

    Strategy는 동적으로 여러 알고리즘들 중의 하나를 선택할 수 있다. 이러한 알고리즘들은 상속관계와 관련될 수 있거나 공통적인 인터페이스를 구현하여 관련이 없을 수도 있다. Context는 요청에 대한 strategy들을 교체하는 것이므로 요구되는 파생 클래스를 간단하게 호출할 수 있다면 좀더 유연해 질 수 있다. 이 접근은 또한 일기 힘들고 유지하기 힘든 조건 문장들을 피할 수 있다.

    반면에 strategy들은 모든 것을 숨기지 않는다. 클라이언트 코드는 다수의 대안 적인 전략들이 있어야 하고 그들 중 선택에 대한 기준이 있어야 한다. 이것은 클라이언트 프로그래머나 사용자에게 알고리즘적인 결정으로 옮겨진다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Iterator Pattern  (0) 2005.03.01
The Interpreter Pattern  (0) 2005.03.01
The Strategy Pattern  (0) 2005.03.01
The Interpreter Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:01

The Template Pattern

    하나 또는 그 이상의 메소드들을 파생된 클래스에 의해 구현하도록 남기는 부모 클래스를 작성할 때 마다  기본적으로 Template 패턴을 사용한다. Templeate 패턴은 클래스에서 알고리즘을 정의하는 것에 대한 생각을 형식화하지만 서브클래스에서 남겨진 것들에 대해 자세한 구현이 이루어 진다. 바꿔서 말하면 디자인 패턴에서 종종 발생하는 것처럼 base 클래스가 추상 클래스이면 Template 패턴의 간단한 형태를 이용한다.

Motivation

    Template들은 기초적인 것이어서 아마 그것에 관한 생각조차 없이 수 십 번도 더 사용했을 것이다. Template 패턴 이면의 생각은 알고리즘의 몇몇 부분이 잘 정의되고 base 클래스에서 구현될 수 있고 반면에 다른 부분은 파생된 클래스들에 남겨져 구현될 수 있다는 것이다. 또 다른 생각은 여러개의 서브클래스에서 반복되지 않도록 base 클래스에서 만들 수 있는 부분이 있다는 것에 대한 인식이다.

    예를 들어 Strategy 패턴의 예에서 사용했던 PlotPanel 클래스들의 개발에서 우리는 선 그래프나 막대 그래프 모두 데이터를 스케일하는 것과 x 축과 y축을 계산하는 부분이 발견되었다. 
public class PlotPanel extends JPanel {	float xfactor, yfactor;	int xpmin, ypmin, xpmax, ypmax;	float minX, maxX, minY, maxY;	float x[], y[];	Color color;		//--------------------------------------------	public void setBounds(float minx, float miny, float maxx, float maxy) {		minX=minx;		maxX= maxx;		minY=miny;		maxY = maxy;	}                                           		//--------------------------------------------	public void plot(float[] xp, float[] yp, Color c) {		x = xp;      //copy in the arrays		y = yp;		color = c;   //and color				//compute bounds and sclaing factors		int w = getWidth() - getInsets().left - getInsets().right;		int h = getHeight() - getInsets().top - getInsets().bottom;		  		xfactor = (0.9f * w) / (maxX - minX);		yfactor = (0.9f * h)/ (maxY - minY);				xpmin = (int)(0.05f * w);		ypmin = (int)(0.05f * h);		xpmax = w - xpmin;		ypmax = h - ypmin;		repaint();      //this causes the actual plot	}
그러므로 이러한 메소들은 실제적인 plotting을 할 수 있는 능력 없이 모두 PlotPanel 크래스에 속해있는다. plot 메소드가 상수들을 스케일링하고 repaint() 메소드를 호출하는 걸 주목해 보자. 실제 paint 메소드는 파생된 클래스 까지 연기되어 있다. Jpanel 클래스가 항상 paint 메소드를 가지고 있기 때문에 우리는 base 클래스에서 추상 메소드로 선언하는 걸 원하지 않지만, 파생된 클래스에서 재정의할 필요가 있다.

Kinds of Methods in a Template Class

Template는 파생된 클래스에서 이용할 수 있는 네 가지의 메소드를 가지고 있다 :
  1. 서브클래스들이 사용하고자 하는 모든 기본적인 기능을 수행하는 메소드를 완성한다. 이런 것들은 Concrete methods 라 부른다.
     
  2. 전혀 채워 지지 않은 메소들은 파생 클래스에서 구현해야만 하나. 자바에서는 abstract 메소드로 선언한다.
     
  3. 몇몇 연산에 대하여 기본적인 구현을 포함하는 메소드는 파생 클래스에서 재정의 될 수 있다. 이런 것들을 Hook 메소드라 부른다. 물론 이것은 임의적으로 만들 수 있다.
     
  4. 마지막으로 Template 클래스는 어떤 추상이나 hook나 구체적인 메소드의 조합으로 그들 자체를 부를 수 있는 메소드를 포함할 수 있다.

Sample Code

    스크린 상에 삼각형들을 그리는 프로그램을 생각해 보자. 우리는 추상 Triangle 클래스를 가지고 시작할 것이고 그다음 몇몇 특별한 삼각형을 파생시킬 것이다.

   

우리의 추상 Triangle 클래스는 Template 패턴으로 설명된다:
public abstract class Triangle {	Point p1, p2, p3; 		//---------------------------------------	public Triangle(Point a, Point b, Point c) {		//save		p1 = a; p2 = b; p3 = c;	}		//---------------------------------------	public void draw(Graphics g) {		//This routine draws a general triangle		drawLine(g, p1, p2);		Point current = draw2ndLine(g, p2, p3);		closeTriangle(g, current);	}		//---------------------------------------	public void drawLine(Graphics g, Point a, Point b) {		g.drawLine(a.x, a.y, b.x, b.y);	}	//---------------------------------------	//this routine is the "Hook" that has to be implemented	//for each triangle type.	abstract public Point draw2ndLine(Graphics g, Point a, Point b);	//---------------------------------------	public void closeTriangle(Graphics g, Point c) {		//draw back to first point		g.drawLine(c.x, c.y, p1.x, p1.y);	}}
이 Triangle 클래스는 3개의 선의 좌표를 저장하지만, draw 루틴은 단지 첫째와 마지막 선만 그린다. draw2ndLine 메소드는 추상 메소드로 남겨진다. 파생된 클래스는 그리고자 하는 사각형의 종류를 생성하는 세 번째 점으로 이동할 수 있도록 한다.

    이것은 Template 패턴을 이용하는 클래스의 일반적인 예이다. draw 메소드는 두 개의 구체적인 base 클래스 메소드를 호출하고 하나의 추상 메소드를 Triangle로부터 상속 받은 구체적인 클래스에서 구체적으로 재정의 해야 한다.

Drawing a Standard Triangle

    모양에 제한 없이 일반적인 삼각형을 그리기 위해 우리는 간단히 draw2ndLine 메소드를 파생된 stdTrinalgle 클래스에서 구현하였다.   
public class stdTriangle extends Triangle {	public stdTriangle(Point a, Point b, Point c) {		super(a, b, c);	}		public Point draw2ndLine(Graphics g, Point a, Point b) {		g.drawLine(a.x, a.y, b.x, b.y);		return  b;	}}
Drawing an Isoceles Triangle  

    이 클래스는 세 번째 데이터 포인트를 두 변의 길이가 같도록 새로 계산하고 새로운 점을 저장한다.
public class IsocelesTriangle extends Triangle {	Point newc;	int newcx, newcy;	int incr;		public IsocelesTriangle(Point a, Point b, Point c)    {		super(a, b, c);		double dx1 = b.x - a.x;		double dy1 = b.y - a.y;		double dx2 = c.x - b.x;		double dy2 = c.y - b.y;				double side1 = calcSide(dx1, dy1);		double side2 = calcSide(dx2, dy2);				if (side2 < side1) 		incr = -1;		else		incr = 1;				double slope = dy2 / dx2;		double intercept = c.y - slope* c.x;				//move point c so that this is an isoceles triangle		newcx = c.x; newcy = c.y;				while(Math.abs(side1 - side2) > 1) {			newcx += incr;    //iterate a pixel at a time until close			newcy = (int)(slope* newcx + intercept);			dx2 = newcx - b.x;			dy2 = newcy - b.y;			side2 = calcSide(dx2, dy2);		}		newc = new Point(newcx, newcy);	}		//--------------------------------------	//calculate length of side	private double calcSide(double dx, double dy) {		return Math.sqrt(dx*dx + dy*dy);	}
Triangle 클래스가 draw 클래스를 호출했을 때 새로운 버젼의 draw2ndLine 메소드를 부르고 새로운 세 번째 점에 선을 그린다. 게다가 삼각형이 정확하게 연결될 수 있도록 새로운 점을 draw 메소드에 반환한다.
	//draws 2nd line using saved new point	public Point draw2ndLine(Graphics g, Point b, Point c)    {		g.drawLine(b.x, b.y, newc.x, newc.y);		return newc;	}

The Triangle Drawing Program

    메인 프로그램은 간단하게 그리고자하는 삼각형의 인스턴스를 생성한다. 그리고 나서, TPanel의 벡터에 그 인스턴스들을 추가한다.
public class TriangleDrawing extends JxFrame {	stdTriangle t, t1;	IsocelesTriangle it;		public TriangleDrawing() {		super("Draw triangles");		TPanel tp = new TPanel();		t = new stdTriangle(new Point(10,10), new Point(150,50), new Point(100, 75));		it = new IsocelesTriangle(new Point(150,100), new Point(240,40), new Point(175, 150));		t1 = new stdTriangle(new Point(150,100), new Point(240,40), new Point(175, 150));		tp.addTriangle(t);		tp.addTriangle(it);		 		getContentPane().add(tp);		setSize(300, 200);		setBackground(Color.white);		setVisible(true);	}		//=======================================	public void paint(Graphics g) {		for (int i = 0; i < triangles.size(); i++) {			Triangle tngl = (Triangle)triangles.elementAt(i);			tngl.draw(g);		}	}					
아래 그림에서 왼쪽은 표준적인 삼각형들을 보여주고 오른쪽은 isoceles 삼각형을 보여주고 있다.

            

Summary and Consequences

    Template 패턴들은 객체지향 소프트웨어에서 상상 발생하고 복잡하거나 분명하지 않는 것을 의도하지 않는다. 이 패턴들은 객체지향 프로그램의 정규적인 부분이고 그것들이 실제적으로 하는 것 보다 더 추상적으로 만들려 할 필요가 없다.

    가장 의미 있는 점은 base 클래스가 메소드들 중 사용될 것은 정의하고 파생 클래스에서 구현할 것은 남겨 둔 다는 것이다. 두 번째는 메소드들의 시퀀스를 부를 수 있는 메소드가 base 클래스에 있을 수 있다는 것이다. Template 메소드는 base 클래스에서 자세한 구현이 완벽하게 이루어지지 않았더라도 일반적인 알고리즘을 정의한다.

    Template 클래스들은 종종 몇 개의 추상 메소드를 가질 수 있고 이 메소드들은 파생된 클래스에서 재정의해야만 한다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Chain of Responsibility Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01
The Chain of Responsibility Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:00

The Mediator Pattern

    어떤 프로그램이 여러 개의  클래스로 만들어져 있을 때 로직과 계산은 이러한 클래스로부터 논리적으로 나뉘어 진다. 그러나 이러한 고립적인 클래스들이 증가함에 따라 이러한 클래스들의 통신 문제가 보다 복잡해졌다. 다른 클래스의 메소드들에 대하여 아는 것이 필요한 클래스가 더 많아지고 클래스들의 구조는 더 얽혀 복잡해 진다. 이것이 프로그램을 읽는 것과 유지를 어렵게 한다. 게다가 프로그램을 변경하는 것이 어려운데 왜냐면 어떤 변화가 여러 개의 다른 클래스들에서 코드에 영향을 줄 지도 모르기 때문이다. Mediator 패턴은 이러한 클래스들 사이의 느슨한 커플링을 진행하여 이러한 문제를 해결할 수 있는 방법을 소개한다. Mediator는 다른 클래스들의 메소드들에 대한 정보를 설명한 오직 하나의 클래스의 이용하여 이 문제를 해결할 수 있다. 클래스들은 변경되었을 때 mediator에게 정보를 준고 Mediator는 정보를 필요로 하는 다른 클래스들에게 정보를 전달해 준다.

An Example System

    여러 개의 버튼과 두 개의 리스트 박스와 입력할 수 있는 텍스트 필드를 갖는 프로그램을 생각해 보자:

       

    프로그램이 시작되었을 때는 Copy 와 Clear 버튼은 비 활성 상태이다.
  1. 좌측의 리스트 박스에서 이름들 중의 하나를 선택했을 때, 편집을 위한 텍스트 필드에 복사가 되고 Copy 버튼은 활성화 된다.
     
  2. Copy 버튼을 눌렀을 때, 그 텍스트는 우측의 리스트 박스에 추가되고 Clear 버튼이 활성화 된다.

        
     
  3. Clear 버튼을 눌렀다면 우측의 리스트 박스와 텍스트 필드는 지워지고 리스트 박스는 선택할 수 없고 두 개의 버튼은 다시 비 활성 상태가 된다.
    이와 같은 사용자 인터페이스는 공통적으로 사람이나 생산물의 목록의 선택에서 사용되어진다. 게다가 이렇게 만든 것보다 삽입, 삭제  와 취소와 같은 기능이 추가되어 더 복잡해 질 수 있다.

Interactions between Controls

시각적인 컨트롤들 사이에 상호작용은 이러한 간단한 예 조차 꾀 복잡하다. 각각의 시각적인 객체는 두 개나 더 이상의 객체들에 관해서 알 필요가 있어 아래 그림처럼 관계들이 얽혀 있을 수 있다.

   

    Mediator 패턴은 이 시스템에서 다른 클래스들을 인식할 수 있는 오직 하나의 클래스 두어 이 시스템을 단순화한다. Mediator 와 통신 하는 각각의 컨트롤들을 Colleague 라 부른다. 각각의 Colleague 는 사용자 이벤트를 받았을 때 Mediator 에게 통지하고 Mediator 는 이벤트가 통지 될 수 있는 다른 클래스들을 결정한다. 아래 그림은 상호 작용 구조를 단순화한 것이다.



    Mediator의 장점은 명확하다 -- 그것은 다른 클래스들에 대해 아는 오직 하나의 클래스를 갖고 그래서 다른 클래스들 중의 하나가 바뀌거나 다른 인터페이스 컨트롤 클래스가 추가된다면 그 클래스는 변경할 필요가 있다.

Sample Code

이 프로그램을 자세하게 고려하고 각각의 컨트롤이 어떻게 구축할 지를 결정해 보자. Mediator 클래스를 사용하는 프로그램 작성에서 주요 차이점은 각각의 클래스들은 Mediator의 존재를 알 필요가 있다. Mediator의 인스턴스를 만들어 시작하고 각 클래스의 생성자에 Mediator의 인스턴스를 전달한다. 
Mediator med = new Mediator();kidList = new KidList(med);tx = new KTextField(med);Move = new MoveButton(this, med);Clear = new ClearButton(thism med);med.init();
    우리는 각 컨트롤에 대해 새로운 클래스들을 만들었기 때문에 각 클래스 내에서 mediator 연산자를 조정할 수 있다.

    두 개의 버튼은 Command 패턴을 사용했고 초기화 되는 동안 Mediator에 등록하였다. CopyButton에 대한 코드는 아래와 같다 :
public class MoveButton extends JButton implements Command {	Mediator med;            //copy of the Mediator		public MoveButton(ActionListener fr, Mediator md) {		super("Copy");         //create the button		addActionListener(fr); //add its listener		med = md;              //copy in the Mediator instance		med.registerMove(this); //register with the Mediator   	}		public void Execute() {		//execute the copy		med.Move();	}}
Clear 버튼은 거의 비슷하다.
   
    아이의 이름 목록은 최근에 예에서 사용한 것에 기본을 두고 있지만 리스트의 데이터 로딩과 Mediator에 등록을 하기 위해 확장을 하였다. 추가적으로 우리는 ListSelectionListener를 구현하였고 어떤 리스트 항목을 누르건 Mediator에 정보를 줄 수 있도록 하였다.
public class KidList extends JawtList implements ListSelectionListener {	KidData kdata;	Mediator med;		public KidList(Mediator md) {		super(20);		kdata = new KidData ("50free.txt");		fillKidList();		med = md;		med.registerKidList(this);		addListSelectionListener(this);	}	    	//----------------------------------	public void valueChanged(ListSelectionEvent ls) {		JList obj = (JList)ls.getSource();		if (obj.getSelectedIndex() >= 0)		med.select();	}		//----------------------------------	private void fillKidList() {		Enumeration ekid = kdata.elements();		while (ekid.hasMoreElements()) {			Kid k =(Kid)ekid.nextElement();			add(k.getFrname()+" "+k.getLname());		}	}}
    텍스트 필드는 그 자체를 mediator에 등록을 하기 때문에 훨씬 간단하다.
public class KTextField extends JTextField {	Mediator med;		public KTextField(Mediator md) {		super(10);		med = md;		med.registerText(this);	}}
    이러한 클래스들 모두의 공통점은 각각의 것들이 Mediator에 대하여 알고 Mediator에게 그것의 존재를 알려줘 Mediator는 적절한 시기에 명령들을 보내 줄 수 있다.

    Mediator 그 자체는 아주 간단하다. 그것은 Copy, Clear 와 Select 메소드를 지원하고 각 컨트롤들을 위해 메소드들을 등록해 둔다:
public class Mediator {	private ClearButton clearButton;	private MoveButton moveButton;	private KTextField ktext;	private KidList klist;	private PickedKidsList picked;		public Mediator() {	}	//------------------------------------	public void Move() {		picked.add(ktext.getText());		clearButton.setEnabled(true);	}		//------------------------------------	public void init() {		Clear();	}		//------------------------------------	public void Clear() {		ktext.setText("");		moveButton.setEnabled(false);		clearButton.setEnabled(false);		picked.clear();		klist.clearSelection();		System.out.println("cleared");	}		//------------------------------------	public void select() {		String s = (String)klist.getSelectedValue();		ktext.setText(s);		moveButton.setEnabled(true);		System.out.println("selected");	}		//------------------------------------	public void registerClear(ClearButton cb) {		clearButton = cb;	}		//------------------------------------	public void registerMove(MoveButton mv) {		moveButton = mv;	}		//------------------------------------	public void registerText(KTextField tx) {		ktext = tx;	}		//------------------------------------	public void registerPicked(PickedKidsList pl) {		picked = pl;	}		//------------------------------------	public void registerKidList(KidList kl) {		klist = kl;	}}
시스템의 초기화

    Mediator를 가장 정교하게 하는 연산자 중의 하나는 요구되는 상황에 대한 모든 컨트롤의 초기화이다. 언제 우리가 프로그램을 시작할 때 각 컨트롤들은 기본 상태에서 알려져 있어야 한다. 왜냐면 이러한 상태들은 프로그램이 진화함에 따라 변경될 수 있기 때문에 우리는 간단히 요구되는 모든 상태에 대해 세팅해 주는 init 메소드를 Mediator에 만들었다. 이 경우에 상태는 Clear 버튼에 의해 이루어지는 것과 같고 우리는 간단하게 이 메소드를 호출할 수 있다 :

public void init() {
    Clear();
}

Mediators and Command Objects

이 프로그램에서 두 개의 버튼은 command 객체들이고 이러한 버튼들을 초기화 할 때 ActionListener로 사용자 인터페이스 프레임에 등록을 하였다. 우리가 앞에서 보았던 것처럼 이것은 간단하게 버튼 클릭 이벤트를 처리한다.

public void actionPerformed(ActionEvent e) {
    Command comd = (Command)e.getSource();
    comd.Execute();
}
대안적으로 우리는 자신의 리스너를 가진 상속 받은 클래스를 등록하여 Mediator에 직접 결과를 전달할 수도 있다.

    그러나 두 가지 모두 Command 패턴 단원에서 보았던 문제들에 대한 해결책 중의 하나이다.

Consequences of the Mediator Pattern

  1. Mediator는 프로그램에서 객체들 사이의 가능한 느슨한 커플링을 만든다. 또 그 밖에 것들이 여러 객체들 사이에서 방해할 수 있는 행동을 줄여줄 수 있다.
     
  2. 간단하게 바꾸거나 Mediator의 하위 클래스로 프로그램의 행위를 바꿀 수 있다.
     
  3. Mediator 접근은 새로운 Colleague를 프로그램의 다른 부분을 변경하는 것 없이 추가할 수 있다.
     
  4. Mediator는 사용자 인터페이스에서 나머지 객체들이나 메소들에 대해 너무 많이 아는 Command 객체의 문제를 해결한다.
     
  5. Mediator는 바꾸기 힘들고 유지하기 힘든 복잡한 덩어리가 될 수 있다. 종종 Mediator에 주어진 책임들을 수정하여 이 상황을 개선할 수 있다. 각 객체는 자신의 일을 수행하고 Mediator는 단지 객체들 사이의 상호작용만 관리한다.
     
  6. 각각의 Mediator는 각각의 Colleague들을 호출할 수있는 메소드를 가지고 있고  각각의 Colleague가 이용하는 메소드들이 무엇인지를 알 수 있도록 작성된 클래스이다. 이것이 다른 프로젝트에서 Mediator 코드가 재사용되는 것을 어렵게 한다. 반면에 Mediator들은 아주 간단한고 이 코드를 작성하는 것이 어떤 방법보다 객체들의 복잡한 상호작용을 관리하는 것 보다 훨씬 더 쉽다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Chain of Responsibility Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Chain of Responsibility Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:00

The Chain of Responsibility Pattern

    Chain of Responsibility 패턴은 다른 클래스들의 능력에 관해 아는 것 없이 어떤 요청을 조정하려 하는 클래스들을 허용하는 패턴이다.  그것은 이러한 클래스들 사이에 느슨한 커플링을 제공한다; 오직 공통적인 링크는 그것들 사이에서 전달되는 요청이다. 그 요청은 클래스들 중의 하나가 그것을 다룰 수 있을 때 까지 전달된다.

    그러한 체인 패턴 중의 하나의 예는 도움말 시스템이다.
도움말에 대한 영역을 선택했을 때, 컨트롤들이 그 체인의 ID 나 이름을 진행시킨다. 우리가 "New" 버튼을 선택했을 때를 가장해 보자. 만약 첫 번째 모듈이 New 버튼을 조정할 수 있다면 그것을 도움말 메시지를 표시한다. 그렇지 않다면 다음 모듈에 대한 요청을 진행시킨다. 결국, 메시지는 일반적으로 어떻게 버튼들이 작동하는가를 나타낼 수 모든 버튼들의 클래스로 진행된다. 거기에 어떤 일반적인 버튼 도움말이 없다면, 그 메시지는 일반적으로 시스템이 어떻게 동작하는지를 말해줄 수 있는 일반적인 도움말 모듈로 진행된다. 그것조차 없다면 그 메시지는 사라지게 되고 어떤 정보도 나타나지 않는다. 아래의 그림은 이러한 과정을 설명한 그림이다.

       
    이 예에서 우리가 관찰할 수 있는 두 가지 의미는 첫째는 체인은 가장 특수한 것에서 가장 일반적인 것으로 구조화 된다는 것이고, 둘째는 모든 경우에서 요청은 책임을 만들어 내는 담보가 없다는 것이다.

Applicability

우리는 다음과 같은 상황에서 Chain of Responsibility를 사용한다.
  • 요청에 대해 조정할 수 있는 하나 이상의 핸들러를 가지고 있고, 사용하는 핸들러를 알아낼 방법이 없을 때. 핸들러는 반드시 체인에 의하여 자동적으로 결정되어야 한다.
  • 명확하게 결정하는 것 없이 여러 객체들 중 하나의 객체에 대한 요청을 이끌러 내려 할 때
  • 요청들을 조정할 수 있는 동적인 객체들의 집합을 수정할 수 있기를 원할 때

Sample Code

    입력된 요청에 따라 결과들을 보여 줄 수 있는 간단한 시스템을 고려해 보자. 이러한 요청들이 있을 수 있다.
  • Image filenames
  • General filenames
  • Colors
  • Other commands
    세 가지 경우에 대해서는 구체적인 요청의 결과를 나타낼 수 있고, 마지막 경우에는 요청한 텍스트 그 자체만 나타낼 수 있다.

       

    위의 예제에서, "ribbons"이라 입력을 하면 "ribbon.jpg" 가 나타나는 걸 볼 수 있다. 그리고 나서, "Sender"라고 입력을 하면, 리스트 박스에서 "Sender.class" 파일일 하이라이트 되는 것을 볼 수 있다. 그 다음, "blue" 라고 입력을 하면 아래의 중앙 패널에 파란색이 칠해지는 걸 볼 수 있다. 마지막으로 파일이름이나 색이 아닌 다른 것을 입력하면 우측이 리스트 박스에 입력한 내용이 추가되는 걸 볼 수 있다.

       

    Responsibility of Chain 프로그램을 작성하기 위해, 먼저 추상 Chain 클래스를 작성한다.
	public interface Chain {	public abstract void addChain(Chain c);	public abstract void sendToChain(String mesg);	public Chain getChain();}
    addChain 메소드는 또 다른 클래스를 클래스들의 체인에 추가한다. getChain 메소드는 메시지가 진행될 때 현재 클래스를 반환한다. 이 두 메소드들은 체인을 동적으로 수정하는 걸 가능하게 하고 존재하는 체인의 가운데에 추가적인 클래스들을 추가한다. sendToChain 메소드는 체인안에서 다음 객체에 메시지를 보낸다.

    우리의 Imager 클래스는 JPanel로 부터 상속되고 Chain interface를 구현한다. 이 클래스는 메시지를 받아 루트 이름이 ".jpg"인 파일을 찾는다. 그런 파일이 있으면 표시를 해준다.
public class Imager extends JPanel implements Chain {	private Chain nextChain;	private Image img;	private boolean loaded;	//------------------------------------------	public void addChain(Chain c) {		nextChain = c;    //next in chain of resp	}	//------------------------------------------	public void sendToChain(String mesg) {		//if there is a JPEG file with this root name		//load it and display it.		if (findImage(mesg))			loadImage(mesg+".jpg");		else			//Otherwise, pass request along chain			nextChain.sendToChain(mesg);	}	//------------------------------------------	public Chain getChain() {		return nextChain;	}          	//------------------------------------------	public void paint(Graphics g) {		if (loaded) {			g.drawImage(img, 0, 0, this);		}	}

The List Boxes

    파일 목록과 인식되지 않는 명령들의 목록 모두 JList 박스들이다. RestList 클래스는 체인의 끝이고 어떤 명령이든 나타내어 준다.그러나 편리한 확장을 허용하기 위해 우리는 메시지를 다른 클래스들로 전달할 수 있다. 
public class RestList extends JawtList implements Chain {	private Chain nextChain = null;		public RestList() {		super(10);     //arg to JawtList		setBorder(new LineBorder(Color.black));	}		public void addChain(Chain c) {		nextChain = c;	}		public void sendToChain(String mesg) {		add(mesg);        //this is the end of the chain		repaint();		if(nextChain != null)		nextChain.sendToChain(mesg);	}		public Chain getChain() {		return nextChain;	}}        
    FileList 클래스는 꾀나 유사하고, addChain 과 getChain 메소드의 반복을 피하기 위하여  RestList 클래스로부터 파생될 수 있다. 유일한 차이는 리스트가 초기화 될 때 현재 디렉토리에서 파일들의 목록을 읽어 들이고 요청을 받은 파일이 있는지 찾아 본다.
public class FileList extends RestList {	String files[];	private Chain nextChain;		public FileList() {		super();		setBorder(new CompoundBorder(new EmptyBorder(5,5,5,5), new LineBorder(Color.black)));		String tmp = "";		File dir = new File(System.getProperty("user.dir"));		files = dir.list();				for(int i = 0; i < files.length; i++) {			for(int j = i; j < files.length; j++) {		     		if(files[i].toLowerCase().compareTo(files[j].toLowerCase()) > 0) {				         tmp = files[i];				         files[i] = files[j];				         files[j] = tmp;		      		}		     	}		}				for(int i = 0; i < files.length; i++)			add(files[i]);	}		//---------------------------------------		public void sendToChain(String mesg) {		boolean found = false;		int i = 0; 				while ((! found) && (i < files.length)) {			XFile xfile = new XFile(files[i]);			found = xfile.matchRoot(mesg);			if (! found) i++;		}				if(found) {		   	setSelectedIndex(i);		}				else {			if(nextChain != null)		      		nextChain.sendToChain(mesg);		}	}
우리가 위에서 소개한 Xfile 클래스는 어떤 파일의 루트이름과 문자열을 비교하기 위한 matchRoot 메소드를 포함하는 File 클래스의 간단한 자식 클래스이다.

    마지막으로, 우리는 그 체인을 형식화하기 위한 생성자에서 이러한 클래스들을 연결시킨다.
//set up the chain of responsibilitysender.addChain(imager);imager.addChain(colorImage);colorImage.addChain(fileList);fileList.addChain(restList);

Consequences of the Chain of Responsibility

  1. 이 패턴의 주요 목적은 객체들 사이의 커플링을 제거하는 것이다. 하나의 객체만 어떻게 다른 객체들에게 요청을 하는지만 알면 된다.
     
  2.  이 접근 방법은 또한 객체들 사이의 분산 responsibilities 에 대해서 유연성을 줄 수 있다. 어떤 객체이든 요청들을 만족할 수 있고, 실행시 체인과 responsibilities를 변경할 수 있다.
     
  3. 장점은 요청을 조정할 수 있는 어떤 객체가 어느것이든 될 수 없다는 것이다. 그러나 체인에서 마지막 객체는 조정할 수 없는 어떤 요청을 버릴 수 있다.
     
  4. 마지막으로, 자바는 다중 상속을 제공하지 않기 때문에, 기본 체인 클래스는 추상클래스보다는 인터페이스를 필요로 한다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Mediator Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
The Chain of Responsibility Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:00

The Observer Pattern

    복잡한 윈도우 세계에서 우리는 종종 데이터를 하나 이상의 폼으로 동시에 보여주고 싶고 그 데이타에서 어떤 변화라도 반사하여 보여주고 싶을 때가 있다. 예를 들어 주식 가격의 변화를 그래프와 테이블이나 리스트로 표현할 지도 모른다. 매 시간 마다 가격은 변하고 우리의 어떤 조작 없이 동시에 변화를 두 가지 방법으로 표현하기를 기대하게 될 것이다.

    우리는 우리가 행위를 볼 수 있는 Excel과 같은 윈도우 어플리케이션들이 있기 때문에  행위의 정렬을 기대한다. 이런 방식으로 작동하는 프로그램에 대해서는 Observer 디자인 패턴이 유용하게 사용될 수 있다.

    Observer 패턴은 데이터를 포함하는 객체는 데이터를 보여주는 객체로부터 분리된다는 것을 가정하고 이 보여주는 객체들은 데이터의 변화를 관찰한다(observe). 아래 그림은 설명을 위한 간단한 그림이다.

       

    우리가 Observer 패턴을 구현할 때, 우리는 일반적으로 주어(Subject)로서 데이터를 참조하고 Observers로 각각의 디스플레이를 참조한다. 각각의 observer들은 Subject에서 public 메소드를 호출하여 데이터에 그것의 관심을 등록한다. 그 다음 각 observer는 Subject가 데이터가 변경될 때 호출하기 위한 인터페이스로 알려져 있다. 우리는 이러한 인터페이스를 아래처럼 정의할 수 있다.
abstract interface Observer {	//notify the Observers that a change has taken place	public void sendNotify (String s);}abstract interface Subject {	//tell the Subject you are interested in changes	public void registerInterested (Observer obs);}
이런 추상 인터페이스들을 정의하여 얻는 이점은 각각의 클래스 객체가 이 인터페이스들을 구현하기를 원하는 그 객체들의 어떤 정렬이건 작성할 수 있고, 이 객체들을 Subject 타입으로 선언할 수 있고 Observer가 어떤 일을 하는가는 문제가 되지 않는다.

Watching Colors Change

    이 강력한 개념을 어떻게 사용할 수 있는가를 설명할 수 있는 간단한 프로그램을 작성해 보자. 우리의 프로그램은 아래의 그림처럼 Red, Green 그리고 Blue라 명명된 3개의 라디오 버튼을 포함하는 프레임을 보여준다.

       

    이 메인 윈도우는 Subject 나 데이터 저장 객체이다. 우리는 이 윈도우를 JFC 클래스들을 이용하여 다음 코드처럼 작성하였다:
public class Watch2L extends JFrame 	implements ActionListener, ItemListener, Subject {		JButton close;	JRadioButton red, green, blue;	Vector observers;		//------------------------		public Watch2L() {		super("Change 2 other frames");				//list of ovserving frames		observers = new Vector();				//add panel to cotent pane		JPanel p = new JPanel(true);		p.setLayout(new BorderLayout());		getContentPane().add("Center",p);				//vertical box layout		Box box = new Box(BoxLayout.Y_AXIS);		p.add("Center", box);				//add 3 radio buttons		box.add(red = new JRadioButton("Red"));		box.add(green = new JRadioButton("Green"));		box.add(blue = new JRadioButton("Blue"));				//listen for clicks on radio buttons		blue.addItemListener(this);		red.addItemListener(this);		green.addItemLitener(this);				//make all part of same button group		ButtonGroup bgr = new ButtonGroup();		bgr.add(red);		bgr.add(green);		bgr.add(blue);
    우리의 메인 클래스는 Subject 인터페이스를 구현한 클래스라는 걸 주목하자. 그 것은 이 클래스에서 관심있는 데이터를 등록하기 위한 public 메소드를 제공해야만 한다는 것을 의미한다. 이 메소드는 registerInterest 메소이고 단지 하나의 Vector에 Observer 객체들을 추가한다:
public void registerInterest (Observer obs) {	//adds obserers to list in Vector	observers.addElement(obs);}
    이제 우리가 두 개의 observers를 생성해서 하나는 색을 나타내고 다른 하나는 현재의 색을 리스트 박스에 추가하자.
//-------create observers-----------ColorFrame cframe = new ColorFrame(this);ListFrame lframe = new ListFrame(this);
ColorFrame 윈도우를 생성할 때 우리의 관심있는 데이터를 메인 프로그램에 등록을 한다:
class ColorFrame extends JFrame implements Observers {	Color color;	String color_name = "black";	JPanel p = new JPanel(true);		//---------------------------	public ColorFrame(Subject s) {		super("Color");		//set frame caption		getContentPane().add("Center",p);		s.registerInterest(this);	//register with Subject		setBounds(100, 100, 100, 100);		setVisible(true);	}		//-----------------------------	public void sendNotify(String s) {		//Observer is nofified of change here		color_name = s;	//save color name		//set background to that color		if(s.toUpperCase().equals("RED"))			color = color.red;		if(s.toUpperCase().equals("GREEN"))			color = color.green;		if(s.toUpperCase().equals("BLUE"))			color = color.blue;							setBackground(color);	}		//-----------------------------	public void paint(Graphics g) {		g.drawString(Color_name, 20, 50);	}
그 동안 메인 프로그램에서는 라디오 버튼 중의 하나를 누를 때마다 Observers 벡터에 있는 객체들을 통하여 간단하게 실행되어 이러한 변화들에 대한 관심을 등록하는 각각의 Observer 의 sendNotify 메소드를 호출한다:
public void itemStateChanged(ItemEvent e) {	//responds to radio button clicks	//ifthe button is selected	if(e.getStateChange() == ItemEvent.SELECTED)		notifyObservers((JRadioButton)e.getSource());	}	//----------------------------private void notifyObservers(JRadioButton rad) {	//send text of selected button to all observsers	String color = rad.getText();	for (int i=0; i < observers.size(); i++) {		((Observer)(observsers.elementAt(i))).sendNotify(color);	}}			
    ColorFrame observer의 경우에서, sendNotify 메소드는 배경색과 프레임 안의 패널에 텍스트를 바꾼다. 그러나 ListFrame observer 경우에는 단지 리스트 박스에 새로운 색의 이름을 추가한다. 아래 그림의 최종적인 프로그램이다 :

       
 

The Message to the Media

Subject가 observers에게 보내는 통지(notification)의 종류는 무인인가? 이  제한적인 예에서 통지 메시지는 색 자체를 표현한 문자열이다. 라디오 버튼을 클릭할 때 그 버튼에 대한 캡션을 얻고 observers 에게 그 캡션을 보낸다. 물론 이것은 모든 observer 들이 문자로 표현된 핸들링을 할 수 있다는 것이다. 보다 실제적인 상황에서 이 방법은 항상 사용할 수 있는 것은 아니다. 특히 observer들이 다른 데이터 객체들을 관찰하는데 사용되어 질 수 있다면 이 방법은 사용되지 않을 수 있다. 여기서 우리는 두 가지 간단한 데이터 변환을 시작한다:
  1. 우리는 라디오 버튼에서 라벨을 얻어 observers에 보낸다. 그리고
     
  2. ColorFrame observer 에서 실제적인 색으로 라벨을 변환한다.

    좀더 복잡한 시스템에서 우리는 요구되는 특성을 관찰할 수 있다. 각각의 observer가 메시지를 정확한 데이터 타입으로 변환하는 것보다 이런 변환을 수행할 중간의 Adapter 클래스를 사용할 수 있다.

    observer들이 처리해야 만 할지도 모르는 또 다른 문제는 중심적인 subject 클래스의 데이터가 여러 가지 방법으로 바뀔 수 있는 경우이다. 우리는 데이터의 목록에서 문제되는 것을 지우고 값을 편집하거나 데이터가 보여줄 수 있는 범위를 바꿀 수 있다. 이런 경우에 우리는 observer들에게 다른 변경 메시지를 보내거나 하나의 메시지를 보내고 발생되는 변경의 정렬을 요청하는 observer를 가지고 있을 필요가 있다.

The MVC Architecture of an Observer

JList, JTable 과 JTree 객체는 모두 데이터 모델의 observer들로 작동한다. 사실 JComponent로부터 파생 받은 모든 비쥬얼 컴포넌트들은데이터와 시각적인 표현 사이의 작업이 이와 같은 분배를 가지고 있다. JFC에서는 데이터는 모델로 표현하고 뷰는 비쥬얼 컴포넌트로 표현하는 모델-뷰-컨트롤러(Model-View-Controller : MVC) 아키텍쳐를 사용하였다. 그리고 컨트롤러는 모델과 뷰 객체들 사이의 통신이며 분할된 클래스이거나 모델이나 뷰에서 상속 받을 수도 있다.

Consequences of the Observer Pattern

    Observer들은 Subject들에게 추상적인 커플링을 조장한다. 어떤 Subject는 Subject의 observer들에 대해 자세히 알지 못한다. 그러나 이것은 데이터를 변경시키는 것이 증가할 때 Observer들을 연속적이거나 반복적으로 업데이트를 해야 하는 단점을 가질 수 있다. 업데이트 비용이 높다면 Observer들이 너무 자주 통보 받지 않도록 변경을 관리하는 어떤 정렬을 도입할 필요가 있다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

The Mediator Pattern  (0) 2005.03.01
The Chain of Responsibility Pattern  (0) 2005.03.01
The Observer Pattern  (0) 2005.03.01
The Template Pattern  (0) 2005.03.01
The Mediator Pattern  (0) 2005.03.01
The Chain of Responsibility Pattern  (0) 2005.03.01
by 꿈꾸는자의 생각의파편들 2005.03.01 00:00
| 1 2 3 |

티스토리 툴바