본문 바로가기

Dev.../패턴자료

The Strategy Pattern

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들은 모든 것을 숨기지 않는다. 클라이언트 코드는 다수의 대안 적인 전략들이 있어야 하고 그들 중 선택에 대한 기준이 있어야 한다. 이것은 클라이언트 프로그래머나 사용자에게 알고리즘적인 결정으로 옮겨진다.

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

The Iterator 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
The Chain of Responsibility Pattern  (0) 2005.03.01