노무현 대통령 배너

Abstract Factory Pattern 추상 팩토리 패턴을 액션스크립트로 컨버팅 – Head First Design Pattern

by on 5.30, 2009, under Design Pattern

헤드퍼스트 디자인 패턴의 제 4 장의 내용은 팩토리 패턴 입니다. 팩토리 패턴에 관련한 포스트는 아래 세 개의 목록이 계속 연결되는 내용이므로 차례대로 읽어나가면 이해에 도움이 될 수 있을 것 입니다.

  1. Simple Factory 심플 팩토리 : 팩토리 패턴의 워밍업
  2. Factory Method Pattern 팩토리 메서드 패턴을 액션스크립트로 컨버팅
  3. Abstract Factory Pattern 추상 팩토리 패턴을 액션스크립트로 컨버팅 (현재 글)

pizza_factory우선 추상 팩토리 패턴의 정의를 보도록 하죠.

“추상 팩토리 패턴 – 추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있습니다.”

앞서 다룬 팩토리 메서드 패턴에서도 하위형으로 NYPizzaStore 구상 객체를 만들고 상위형이자 추상층인 PizzaStore 로 업 캐스팅 하는 부분이 있습니다만,[01] 팩토리 메서드 패턴의 핵심은 그 부분에 있는 것이 아니라, 객체 생성을 서브클래스에 캡슐화 시킨다는 훨씬 중요한 내용에 있습니다.

그러나 이 포스트에서 다루게 되는 추상 팩토리 패턴은 본격적으로 인터페이스와 업 캐스팅을 통해서 객체 자동생성을 구현하게 되고, 그것을 통해 생산성과 직접적인 연관을 가지게 됩니다. 결국 높은 객체 생산 효율을 추구하는 디자인 패턴인 것이죠.

이어지는 피자가게 프랜차이즈 스토리에 대한 설명을 약간 해야 할 것 같습니다. 객체마을 피자가게가 인기가 올라가자 뉴욕과 시카고에 분점을 내기로 한 것까지가 지난번 팩토리메서드의 이야기였습니다. 그런데 일부 분점에서는 자잘한 재료를 더 싼 재료로 바꿔서 원가를 줄이고 마진을 올리고 있다는 정보를 들어왔습니다. 이런 현상이 심해지면 객체마을 피자 브랜드의 품질에 타격이 올 수 있기 때문에 각 분점에서 임의로 재료를 공급해서 조달하는 것을 막고자 하는 것이 이번 추상 팩토리 패턴의 목적 입니다.

각 분점에 재료를 공급하기 위한 원재료 공장을 만들어야 합니다. 원재료 공장의 인터페이스를 살펴보죠.

0
1
2
3
4
5
6
7
8
9
10
11
package
{
	public interface IPizzaIngredientFactory
	{
		function createDough():IDough;
		function createSauce():ISauce;
		function createCheese():ICheese;
		function createVeggies():Array;
		function createPepperoni():IPepperoni;
		function createClam():IClams;
	}
}

피자에 들어가는 각각의 재료를 모두 인터페이스 메서드로 만들었습니다. 이전 버전과는 달리 원재료가 어떻게 공급되는지 표현하기 위해 뭉뚱그린 표현인 토핑(배열)을 없애고 야채(배열), 페퍼로니, 치즈, 조개로 좀더 세분화하였습니다.

위의 인터페이스를 구현하는 뉴욕 피자 원료 공장을 보면,

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
package
{
	public class NYPizzaIngredientFactory implements IPizzaIngredientFactory
	{
		public function createDough():IDough
		{
			return new ThinCrustDough();
		}
 
		public function createSauce():ISauce
		{
			return new MarinaraSauce();
		}
 
		public function createCheese():ICheese
		{
			return new ReggianoCheese();
		}
 
		public function createVeggies():Array
		{
			var veggies:Array = [ new Garlic(), new Onion(), new Mushroom(), new RedPepper() ]
			return veggies;
		}
 
		public function createPepperoni():IPepperoni
		{
			return new SlicedPepperoni();
		}
 
		public function createClam():IClams
		{
			return new FreshClams();
		}
	}
}

뉴욕 스타일 피자에 맞는 각 재료들을 모두 구현하고 있습니다. 어떤 피자 주문이 들어오더라도 일단 뉴욕 스타일 피자라면 재료공급이 가능한거죠.[02]

그러면, 피자가게에서는 이 재료 공장을 어떻게 이용하는지 볼까요?

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
package
{
	public class NYPizzaStore extends PizzaStore
	{
		public override function createPizza( $item:String ):Pizza
		{
			var pizza:Pizza = null;
			//구상객체로 생성해서 상위형으로 캐스팅합니다. 이게 바로 핵심이죠.
			var ingredientFactory:IPizzaIngredientFactory = new NYPizzaIngredientFactory();
 
			if ( $item == "cheese" )
			{
				//이 클래스에서 사용하는 재료공장은 위에서 이미 정해졌죠.
				pizza = new CheesePizza( ingredientFactory )
				//대신 이름은 여기서 달아줘야 합니다.
				pizza.setName( "뉴욕스타일 치즈피자" )
			}
			else if ( $item == "pepperoni" )
			{
				pizza = new PepperoniPizza( ingredientFactory )
				pizza.setName( "뉴욕스타일 페퍼로니피자" )
			}
			else if ( $item == "clam" )
			{
				pizza = new ClamPizza( ingredientFactory )
				pizza.setName( "뉴욕스타일 조개피자" )
			}
			else if ( $item == "veggie" )
			{
				pizza = new VeggiePizza( ingredientFactory )
				pizza.setName( "뉴욕스타일 야채피자" )
			}
 
			return pizza;
		}
	}
}

NYPizzaStore 에 재료를 공급하는 공장을 NYPizzaIngredientFactory 으로 인스턴스변수를 지정했습니다. 어떤 재료공장의 재료를 사용해야하는지 알고 있으므로 CheesePizza를 만들기만 하면 레지오노 치즈와 씬 크러스트 도우를 사용하게 되는겁니다.
팩토리 메서드 패턴에서의 이 부분을 기억하시나요? new NYStyleCheesePizza() 로 치즈피자의 스타일을 지정한 구상객체를 만들었죠. 왜냐하면, 팩토리 메서드때에는 재료를 공급하는 시스템이 없었으니까 어떤 특정한 치즈를 사용하라 라고 지정해 줘야만 했기 때문이죠.

치즈피자 클래스를 볼까요?

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package
{
	public class CheesePizza extends Pizza
	{
		private var ingredientFactory:IPizzaIngredientFactory;
 
		public function CheesePizza( $ingredientFactory:IPizzaIngredientFactory )
		{
			this.ingredientFactory = $ingredientFactory;
		}
 
		public override function prepare():void
		{
			trace( "주문접수 : " , name );
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			trace( toString() )
		}
	}
}

시카고 분점이나 뉴욕 분점이나 치즈피자에 들어가는 재료의 종류는 동일합니다.[03] 그러므로 피자를 만드는 방법은 동일하지만,[04] 이 CheesePizza 클래스를 NYPizzaStore 에서 사용하면 NYPizzaIngredientFactory 의 치즈를 사용하게 되므로 ReggianoCheese 객체를 사용하게 되고, ChicagoPizzaStore 에서 사용하면 ChicagoPizzaIngredientFactory 의 재료를 사용하게 되어 MozzarellaCheese 객체를 사용하게 되는 것입니다.

Pizza 클래스는 팩토리 메서드 패턴에서도 추상층의 역할을 하긴 했지만, 이번에는 추상층일 뿐만 아니라 정말로 추상 메서드를 사용합니다. 위의 CheesePizza 클래스에서 Pizza 클래스를 확장하고 있고 prepare() 메서드를 override 하고 있는것을 볼 수 있습니다.

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package
{
	public class Pizza
	{
		protected var name:String;
		protected var dough:IDough;
		protected var sauce:ISauce;
		protected var veggies:Array = new Array();
		protected var cheese:ICheese;
		protected var pepperoni:IPepperoni;
		protected var clam:IClams;
 
		public function getName():String
		{
			return name;
		}
 
		public function setName( $name:String ):void
		{
			this.name = $name
		}
 
		public function prepare():void
		{
			throw new Error( "prepare() 추상 메서드" )
		}
 
		public function bake():void
		{
			trace( "피자를 굽습니다 :", name )
		}
 
		public function cut():void
		{
			trace( "피자를 자릅니다 :", name )
		}
 
		public function box():void
		{
			trace( "피자를 포장합니다 :", name )
		}
 
		public function toString():String
		{
			var display:String = "";
			display += "-----" + name + "를 준비합니다 -----";
 
			if ( dough != null )
				display += "\n도우 반죽중... : " + dough.toString();
 
			if ( sauce != null )
				display += "\n소스 추가중... : " + sauce.toString();
 
			if ( cheese != null )
				display += "\n치즈 추가중... : " + cheese.toString();
 
			if ( veggies.length > 0 )
			{
				var t:String = "";
				for (var i:uint = 0; i < veggies.length; i++ )
				{
					t += veggies[ i ].toString() + ", ";
				}
				display += ("\n야채 추가중... : " + t);
			}
			if ( clam != null )
				display += "\n조개 추가중... : " + clam.toString();
 
			if ( pepperoni != null )
				display += "\n페퍼로니 추가중... : " + pepperoni.toString();
 
			return display
		}
	}
}

결국 Pizza 클래스는 모든 피자 만드는 방법을 포괄하고 있고, CheesePizza 클래스는 모든 치즈피자 (시카고든 뉴욕이든) 만드는 방법을 포괄하고 있습니다. 이렇게 할 수 있는 이유는 각 분점에 재료를 공급하는 공장이 알아서 그 분점에 맞는 재료를 공급해 주기 때문입니다.

호스트코드는 팩토리 메서드 패턴과 동일합니다. 실행 결과도 동일하죠.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package
{
	import flash.display.Sprite;
 
	public class Main extends Sprite
	{
		public function Main():void
		{
			//팩토리 메서드 패턴의 호스트코드 와 동일하게 사용
			var nyStore:PizzaStore = new NYPizzaStore();
			var chicagoStore:PizzaStore = new ChicagoPizzaStore()
 
			var pizza:Pizza = nyStore.orderPizza( "cheese" )
			trace( "주문한 피자 나왔습니다 :", pizza.getName() )
 
			pizza = chicagoStore.orderPizza( "veggie" )
			trace( "주문한 피자 나왔습니다 :", pizza.getName() )
		}
	}
}

trace 도 역시 거의 동일합니다.

주문접수 : 뉴욕스타일 치즈피자
—–뉴욕스타일 치즈피자를 준비합니다 —–
도우 반죽중… : 씬 크러스트 도우
소스 추가중… : 마리나라 소스
치즈 추가중… : 레지오노 치즈
피자를 굽습니다 : 뉴욕스타일 치즈피자
피자를 자릅니다 : 뉴욕스타일 치즈피자
피자를 포장합니다 : 뉴욕스타일 치즈피자
주문한 피자 나왔습니다 : 뉴욕스타일 치즈피자

주문접수 : 시카고스타일 야채피자
—–시카고스타일 야채피자를 준비합니다 —–
도우 반죽중… : 익스트라 크러스트 도우
소스 추가중… : 플럼토마토 소스
치즈 추가중… : 모짜렐라 치즈
야채 추가중… : 검은 올리브, 시금치, 가지,
피자를 굽습니다 : 시카고스타일 야채피자
피자를 자릅니다 : 시카고스타일 야채피자
피자를 포장합니다 : 시카고스타일 야채피자
주문한 피자 나왔습니다 : 시카고스타일 야채피자

이렇게 구조를 짜 놓았다면 기존에 이미 잘 돌아가고 있는 분점의 코드는 전혀 수정을 하지 않고 계속 확장을 해 나갈 수 있게 됩니다. 이후로 분점이 다른곳에도 생겨도 재료공장만 추가로 구현하기만 하면 문제없이 확장이 가능하다는 이야기도 됩니다. 또한 새로운 피자메뉴가 개발되어도 Pizza 클래스를 확장하여 새로운 피자제품 클래스만 만들어 내면 모든 분점이 이 새로운 피자 메뉴를 만들 수 있게 되겠죠.

새 클래스를 만들어서 테스트 해 봤더니 컴파일 에러가 났다면? 새로 추가한 클래스 내부의 에러에 국한해서 살펴보면 됩니다. 추상층으로 거슬러 올라가 코드가 어디부터 틀렸는지 죄다 살펴볼 필요가 없는 것입니다.

추상 팩토리 패턴은 각 재료에 대한 모든 구상 클래스가 있습니다. 클래스 갯수가 너무 많아 일일히 이 포스트에 늘어놓진 않았습니다. 컴파일 해보실 분은 아래에 있는 총 35개의 클래스 전체를 다운로드해서 컴파일 해보시기 바랍니다.

 

Abstract Factory Pattern Example 액션스크립트 코드 다운로드 (187)
이 글을 복사해서 퍼가시는건 허용하지 않습니다. 글의 주소를 다른곳에 알려주시는 것은 환영합니다.
  1. 이 부분이 팩토리 메서드와 추상 팩토리의 경계를 모호하게 만드는 것 같습니다. []
  2. 피자 종류에 따라 사용하지 않는 재료도 일단은 모두 구현을 해 놓았다는 의미 입니다. []
  3. 도우, 치즈, 소스의 종류가 틀리지만 어쨌건 도우가 들어가고, 치즈도 들어가고, 소스도 들어간다는 사실 자체가 동일하다는 의미입니다. []
  4. 구체적으로는 제작순서가 동일하다 라고 할 수 있겠습니다. []

관련된 글

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

3 Comments for this entry

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