노무현 대통령 배너

Strategy Pattern 스트래티지 패턴을 액션스크립트로 컨버팅 – Head First Design Pattern

by on 5.16, 2009, under Design Pattern

strategy_pattern_chess이번에는 Head First Design Pattern 의 제 1 장 내용인 스트래티지 패턴(Strategy Pattern : 전략 패턴) 입니다. 역시 이번에도 자바 코드를 액션 스크립트로 컨버팅 하였습니다.

책에는 스트래티지 패턴을 이렇게 정의하고 있습니다.

“스트래티지 패턴(Strategy Pattern)에서는 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다. 스트래티지 패턴을 사용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다”

슈퍼클래스에 구체적인 행동에 대한 메서드가 있는 경우 서브클래스에서 의도하지 않은 오류가 발생할 수 있는 가능성이 있습니다. 오리는 무조건 날 수 있을거라고 생각하고 슈퍼클래스인 Duck에 fly() 메서드를 만들고 구체적으로 날라다니는 구현을 한 경우 고무오리나 모형오리도 날라다니는 오류를 책에서 예를 들어 설명하고 있습니다.

그래서 Duck 클래스에서 개별적인 행동을 빼내서 인터페이스로 만들고 그 인터페이스를 구상하는 클래스들을 만들고, Duck 클래스를 상속받아 세부적인 구현을 하는 서브클래스들에서 인터페이스를 통해 구상 클래스를 사용하게 됩니다.

먼저 Duck 클래스에서 구체적인 행동인 날아다니는 행동을 인터페이스로 만듭니다.
책에는 인터페이스 이름을 FlyBehavior 라고 하였지만, 뒷 부분을 단어를 빼고 인터페이스임을 확실하게 하기 위하여 앞에 I 를 붙여 IFly 로 인터페이스를 만들었습니다.

0
1
2
3
4
5
6
package
{
	public interface IFly
	{
		function fly():void;
	}
}

그리고 IFly 를 구상하는 구상 클래스를 날아다니는 형태별로 작성합니다.

0
1
2
3
4
5
6
7
8
9
package
{
	public class FlyWithWings implements IFly
	{
		public function fly():void
		{
			trace( "날고 있어요!!" )
		}
	}
}
0
1
2
3
4
5
6
7
8
9
package
{
	public class FlyNoWay implements IFly
	{
		public function fly():void
		{
			trace( "저는 못 날아요" )
		}
	}
}
0
1
2
3
4
5
6
7
8
9
package
{
	public class FlyRocketPowered implements IFly
	{
		public function fly():void
		{
			trace( "로켓 추진으로 날아갑니다." )
		}
	}
}

그리고 Duck 클래스를 아래와 같이 만들었습니다.  Duck 클래스는 슈퍼클래스이자 구상클래스 입니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package
{
	public class Duck
	{
		//서브클래스에서 사용할 수 있도록 protected 로 변수선언
		protected var quackBehavior:IQuack;
		protected var flyBehavior:IFly;
 
		public function Duck() {}
 
		//오리의 모양새를 표현하는 메서드. 서브클래스에서 override 하지 않으면 컴파일 에러를 던지도록 함
		public function display():void
		{
			throw new Error( "추상 메서드입니다 override 하세요" )
		}
 
		public function performQuack():void
		{
			quackBehavior.quack();
		}
 
		public function performFly():void
		{
			flyBehavior.fly();
		}
 
		//서브클래스에서도 항상 똑같을 것으로 예상되는 메서드는 Duck 클래스에서 구현해 버립니다.
		public function swim():void
		{
			trace( "모든 오리는 물에 뜹니다. 가짜 오리도 뜨죠." )
		}
 
		//아래의 두개의 메서드는 인터페이스로 빼낸 두가지의 행동을 동적으로 바꾸기 위한 setter 메서드들
		public function setIFly( $fly:IFly ):void
		{
			flyBehavior = $fly;
		}
 
		public function setIQuack( $quack:IQuack ):void
		{
			quackBehavior = $quack;
		}
	}
}

설명은 클래스 내부에 주석으로 달았습니다.

Duck 클래스를 확장(상속)하여 MallardDuck(물오리) 클래스를 만들었는데 아래와 같이 Duck에서 상속한 변수에 MallardDuck에 알맞는 구상클래스[01] 로 생성한 구상객체를 대입합니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package
{
	public class MallardDuck extends Duck
	{
		public function MallardDuck()
		{
			//quackBehavior와 flyBehavior는 Duck에서 상속받은 인스턴스변수
			//인스턴스변수에 구상클래스의 인스턴스(구체적인 행동)를 생성하여 집어넣음
			quackBehavior = new Quack();
			flyBehavior = new FlyWithWings();
		}
 
		//Duck 클래스의 추상 메서드를 override
		public override function display():void
		{
			trace( "n저는 물오리 입니다." )
		}
	}
}

IQuack 인터페이스와 그것을 구상한 클래스들은 IFly와 동일한 구조를 가지고 있습니다. 하단에 모든 코드를 다운받을 수 있도록 해 놓았으니 본문에는 Quack 관련 클래스들을 생략하겠습니다.

ModelDuck(모형오리) 클래스도 만들어서 MallardDuck(물오리) 클래스와는 살짝 다른 구상클래스 객체를 대입합니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package
{
	public class ModelDuck extends Duck
	{
		public function ModelDuck()
		{
			quackBehavior = new MuteQuack();
			flyBehavior = new FlyNoWay();
		}
 
		public override function display():void
		{
			trace( "n저는 모형오리 입니다." )
		}
	}
}

호스트코드[02] 에서는 MallardDuck과 ModelDuck을 Duck으로 상위형변환 하면서 객체를 만들고 동일한 메소드 들을 호출하면 각 구상 객체에 할당된 인터페이스를 구상한 객체에 맞는 메소드를 실행하게 됩니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package
{
	import flash.display.Sprite;
 
	public class Main extends Sprite
	{
		public function Main()
		{
			var mallard:Duck = new MallardDuck();
			mallard.display();		//출력: 저는 물오리 입니다.
			mallard.performQuack();	//출력: 꽥!
			mallard.performFly();	//출력: 날고 있어요!!
 
			var model:Duck = new ModelDuck();
			model.display()		//출력: 저는 모형오리 입니다.
			model.performQuack();	//출력: << 조용~ >>
			model.performFly();	//출력: 저는 못 날아요
 
			//실행중에 오리의 행동을 바꾸고 싶으면 원하는 행동에 해당하는 Duck의 setter 메소드를 호출합니다.
			model.setIFly( new FlyRocketPowered() );
			model.performFly();	//출력: 로켓 추진으로 날아갑니다.
		}
	}
}

이렇게 하면 Duck 에서 상속한 모든 오리들에게 IFly 인터페이스가 나는것과 관련있는 다수의 구상 클래스 중에서 설정된 구상 클래스를 위임 받게 됩니다. 이렇게 함으로써 호스트코드에서는 어떤 오리가 오건간에 항상 똑같이 performFly() 메서드만 호출하면 각각의 오리에 알맞는 날아다니는 동작이 표현되는 것입니다.

이 패턴이 강력한 또한가지 이유는 런타임에 구상 객체를 변경할 수 있다는 점입니다. 위의 호스트코드에서 보듯이 모형오리에게 setIFly( new FlyRocketPowered() ) 를 해 주면 Duck 의 setIFly 메서드가 해당 인터페이스의 구상객체를 FlyRocketPowered 객체로 실시간으로 변경하여, 날지 못하던 모형오리에게 로켓추진을 부여할 수 있게 되는 것입니다.

클래스를 상속하여 오버라이드 하지 않고, 이런식으로 인터페이스를 통해 구상클래스를 합치는 것을 합성[03] 한다고 하고 영어로는 composition 이라고 씁니다. 합성은 객체지향 프로그래밍에서 OOP의 중요한 원칙 중 하나 입니다.

 

Strategy Pattern Example 액션스크립트 코드 다운로드 (169)
이 글을 복사해서 퍼가시는건 허용하지 않습니다. 글의 주소를 다른곳에 알려주시는 것은 환영합니다.
  1. 꽥꽥 거리는 것을 구상한 Quack 클래스와 날아다니는것을 구상한 FlyWithWings 클래스 입니다. []
  2. 도큐먼트 클래스의 코드를 말합니다 []
  3. 구성도 같은 말입니다. []

관련된 글

:, , , , , , , , , , , , , ,

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Meta