DesignPatternSmalltalkCompanion:AbstractFactory

From 흡혈양파의 번역工房
Revision as of 10:23, 8 January 2013 by Onionmixer (talk | contribs) (메소드 > 메서드 수정)
Jump to navigation Jump to search

ABSTRACT FACTORY (DP 87)

객체생성

의도

추상 팩토리는 관련 객체군 (object family) 또는 종속 객체군 생성을 위한 인터페이스를 제공하는 것을 목적으로 한다. 추상 팩토리를 통해 클라이언트는 구체적 클래스(concrete class)를 지정할 필요 없이 어떠한 제품군의 제품이든 추상적으로 생성시킬 수 있다.

구조

Dpsc chapter03 AbstractFactory 01.png

논의

아이러니하게도 패턴이 적용되는 컨텍스트(context)가 패턴 해결책만큼 복잡한 경우가 때때로 발생하는데, 추상 팩토리(abstract factory)가 이의 경우다. 패턴 자체는 사실 꽤 단순하다; 문제 컨텍스트에 많은 부분(part)이 포함되어 있다. 우선 어떤 상황에 추상 팩토리 패턴을 적용할 수 있는지 살펴보도록 한다.[1]

첫째, 우리에게는 컴포넌트 하위부품(subpart)부터 하나씩 시작해서 제품을 구축해야 하는 애플리케이션이 하나 있다. 이 애플리케이션을 이용해 차체, 엔진, 변속기, 차 내부로 나누어지는 자동차를 구현할 수 있을 것이다. 둘째, 이 애플리케이션에서는 단일 제품 내 컴포넌트가 동일한 부품군, 즉 세트로 된 부품이어야 한다: 포드(Ford) 자동차는 포드사에서 만든 엔진과 변속기를 요하는 것이다. 이러한 부품은 포드 군(계열)에 속한다고 할 수 있다. 셋째, 우리는 포드 부품, 토요타(Toyota) 부품, 포르쉐(Porsche) 부품 등 여러 종류의 부품군을 소유한다. 해당 클래스는 클래스 계층구조(class hierarchy)를 통해 적절한 하위계층구조(subhierarchy)로 뻗어 나간다: 엔진은 CarEngine 하위계층구조에, 차체는 CarBody 계층구조에 속하게 된다. 따라서 우리는 이 애플리케이션이, (1) 하나의 부품군으로부터 자동차 컴포넌트를 쉽게 검색하고 부품군들 간의 오류를 허용하지 않으며 (토요타 엔진이 포드 자동차에 사용되는 경우가 없도록), (2) 모든 부품군에 있어 통일된 부품검색코드를 사용할 수 있는 방법이 필요하다. 이에 추상 팩토리 패턴을 사용할 경우 두 가지 조건을 모두 충족시킬 수 있는 것이다. 아래와 같은 자동차 클래스와 자동차 부품 클래스가 있다:

Dpsc chapter03 AbstractFactory 02.png

Dpsc chapter03 AbstractFactory 03.png

Vehicle과 CarPart는 Object의 서브클래스이다. 물론 이러한 클래스는 여러모로 지나치게 단순화시킨 구조이다. 포드와 같은 자동차 회사에서는 자동차, 차체, 엔진, 심지어 엔진 유형까지 (예: 카솔린 구동식 또는 디젤 엔진) 여러 유형의 모델이 있다. 따라서 실제 모델에는 여기서 나타내는 것보다 추상화 단계가 더 많다. 하지만 이 책의 패턴 설명은 최대한 단순하고 쉽게 관리하는 것을 목적으로 한다.

먼저 CarPartFactory라는 추상 팩토리 클래스를 정의함으로써 패턴의 구현을 시작하고자 한다. 이 클래스는 "구체적 클래스를 지정하지 않고 관련 객체군 또는 종속 객체군을 생성할 수 있는 인터페이스를 제공한다" (‘의도’ 단락 참조). 이는 또한 makeCar, makeEngine, makeBody와 같은 추상적 Product-생성 메서드를 정의한다. 그리고 나면 사용자는 제품군마다 하나의 구체적 팩토리 서브클래스를 정의한다. 각 서브클래스는 적절한 부품을 생성하고 반환하기 (return) 위해 제품 생성 메서드를 재정의한다. 따라서 사용자는 Object: 아래에 다음과 같이 새로운 하위계층구조를 추가한다.

Dpsc chapter03 AbstractFactory 04.png

부품 생성 메서드를 구현하기 위해 추상 팩토리 클래스부터 시작하며,

CarPartFactory>>makeCar
   self subclassResponsibi1ity
CarPartFactory>>makeEngine
   self subclassResponsibility
CarPartFactory>>makeBody
   self subclassResponsibility

이후 이러한 메서드를 오버라이드(override)하는 구체적 서브클래스를 추가한다:

FordFactory>>makeCar
   ^FordCar new

FordFactory>>makeEngine
   ^FordEngine new

FordFactory>>makeBody
   ^FordBody new

ToyotaFactory>>makeCar
   ^ToyotaCar new

ToyotaFactory>>makeEngine
   ^ToyotaEngine new

ToyotaFactory>>makeBody
   ^ToyotaBody new

전체적으로 우리 팩토리는 다음과 같은 모습이다.

Dpsc chapter03 AbstractFactory 05.png

추상 팩토리 패턴을 이용 시 이 조각들을 조립하는 것은 팩토리 클라이언트에 달려 있다. 팩토리는 부품이 한 부품군에서 나오도록 보장하지만 부품을 반환하는 일만 한다; 최종 제품으로 조립하는 작업은 하지 않는다. 조립하는 작업은 클라이언트의 일이다.[2](이것이 추상 팩토리와 Builder 패턴 (47) 간 주요 차이점이라는 사실은 추후에 살펴볼 것이다.)


CarAssembler 객체가 팩토리 클라이언트이고, 여기에 CarPartFactory 객체를 참조하는 factory 라는 이름의 인스턴스 변수가 하나 있다고 가정하자.

CarAssembler>>assembler
   | car |
   "Create the top-level part, the car object which
   starts out having no subcomponents, and add and
   engine, body, etc."
   car := factory makeCar. (각주3
   car
      addEngine: factory makeEngine;
      addBody: factory makeBody;
      ...
   ^car

만약 factory가 FordFactory의 인스턴스라면 자동차에 추가되는 엔진은 FordEngine일 것이다; 그리고 만약 factory가 ToyotaFactory의 인스턴스였다면 factory makeEngine에 의해 ToyotaEngine이 생성되어 진행 중인 자동차에 추가될 것이다.

여전히 풀리지 않은 궁금증이 하나 있다. CarAssembler(팩토리 클라이언트)는 어떻게 CarPartFactory의 특정 서브클래스의 인스턴스를 얻을 수 있을까? 그것은 소비자의 선택을 바탕으로 하여 스스로 특정 서브클래스 인스턴스화하기도 하고 외부 객체에 의해 팩토리 인스턴스를 전달받기도 한다. 그러나 두 경우 모두 자동차와 컴포넌트 하위부품을 생성하는 코드는 동일하게 남아 있다. 즉, 모든 CarPartFactory 클래스는 다형적으로 동일한 메시징 프로토콜을 실행하기 때문에 팩토리의 클라이언트는 어떤 유형의 팩토리와 대화 중인지에 대해 신경 쓰지 않는다. 그저 팩토리 프로토콜이 제공하는 일반 메시지를 전송할 뿐이다.[3]

다형성의 힘으로 인해 클라이언트는 다수의 조건문(conditional)보다는 한 가지 버전의 코드를 구현하며, 여전히 어떤 종류의 자동차든 생산할 수 있다. 비교를 들자면, 추상 팩토리 패턴이 없다면 자동차 생성 코드는 다음과 같은 모습일 것이다:

CarAssembler>>assembler
   "Without Abstract Factory."
   | car |
   car := (consumerChoice == #Ford
            ifTrue: [FordCar new]
            ifFalse: [consumerChoice ==#Toyota
               ifTrue: [ToyotaCar new]
               ifFalse: [consumerChoice == #Porsche
                  ifTrue: [PorscheCar new]
                  ifFalse: [...]).
   car addEngine:
         (consumerChoice == #Ford
            ifTrue: [FordEngine new]
            ifFalse: [...]).
   ...
   ^car

여기서 CarAssembler는 어떠한 종류의 자동차를 구축할 것이며, 그 하위부품은 무엇이 될 것인지 스스로 결정하고, 실제 부품의 인스턴스화를 실행한다. 추상 팩토리 해법은 CarAssembler 객체로부터 생기는 모든 행위를 팩토리라는 하나의 구분된 행위로 추상화시킨다. CarAssembler를 특정 자동차 팩토리로 구성한 후에 CarAssembler는 자동차와 하위부품을 생산하기 위해 이 팩토리로 요청하는 것이다.

추상 팩토리 접근법은 좀 더 모듈식이면서 쉽게 확장이 가능한 설계로 만든다. 시스템에 두 가지 유형의 자동차를 추가하려면 복잡한 조건문 집합 내에서 여러 위치에 새 부품을 추가하기 위해 CarAssembler>>buildCar를 다시 찾기보다는 CarPartFactory의 새 서브클래스와 이를 인스턴스화시킬 코드만 있으면 된다.

여기에는 사실상 두 가지의 추상화가 이루어진다. 첫째, 모든 CarPartFactory들은 동일한 메시지 인터페이스를 구현한다. 이는 팩토리 클라이언트가 보내는 CarPartFactory 타입이 정확히 무엇인지 상관하지 않으면서 동일한 부품 생성 메시지를 보낼 수 있게 해준다. 둘째, ConcreteProducts (자동차 부품 클래스)는 각 부품 하위계층구조의 추상 상위클래스(superclass)에 정의된 것과 동일한 인터페이스를 구현한다. 예를 들어, 모든 Car들은 addEngine: 과 addBody: 메시지에 대해 어떻게 응답해야 하는지 알고 있다. 또한 CarBody 객체들은 모두 color:과 color 메시지들을 구현한다. 모든 CarEngine들 또한 이와 마찬가지로 공통된 메시징 인터페이스를 제공한다. 다른 부품의 경우도 마찬가지다.

이번 논의를 상세히 설명하기 위해, [디자인 패턴]에서와 마찬가지로 팩토리 클라이언트가 외부(outsider)로부터 팩토리 객체를 전달받았다고 가정하자. 이는 CarAssembler가 어떤 종류의 CarPartFactory 를 사용하는지 스스로 정확히 알지 못한다는 점을 암시한다. 이는 (1) 자신의 팩토리 객체를 참조하는 인스턴스 변수를 갖기 위해 팩토리 클라이언트 CarAssembler를 정의함으로써, 또는 (2)팩토리 객체를 argument로서 클라이언트의 자동차 생성 메서드로 전달함으로써 달성할 수 있다. 첫 번째 방법을 사용 시 본 사례에서는 CarAssembler 클래스와 관련 메서드를 다음과 같이 정의할 것이다:

Object subclass: #CarAssembler
	instanseVariableNames: 'factory'
	classVariablesNames: ''
	poolDictionaries: ''

CarAssembler>>factory: aCarPartFactory
	"setter method"
	factory := aCarPartFactory
	
CarAssembler class>>using: aCarPartFactory
	"Instance creation method"
	^self new factory: aCarPartFactory

외부 객체, 즉 CarAssembler의 클라이언트는 적절한 팩토리 인스턴스를 이용해 CarAssembler를 생성하여 초기화(initialize)할 것이다. CarAssembler의 클라이언트가 대화형 3D 자동차 시각화 애플리케이션이라고 치자. 사용자는 사용자 인터페이스에서 자동차를 선택할 수 있다; 애플리케이션은 이에 대한 응답으로 화면에 3차원 그래픽의 이미지를 만든다. 사용자는 자동차가 어떻게 생겼는지 살펴보고 인테리어, 엔진 또는 관심 부분을 "살펴보기" (가까이 가기, 내부 모습) 위해 3D 공간을 탐색한다. 자동차는 화면 위의 사용자 버튼의 선택을 바탕으로 구축된다고 가정한다ㅡ사용자는 포드, 토요타, 포르쉐 버튼 중 하나를 클릭하여 선택할 수 있다. 이에 대한 응답으로 사용자 인터페이스 코드는 다음과 같은 작업을 수행할 것이다.

CarAssemblerUI>>fordButtonClicked
	"The user clicked the 'Ford' button."
	| assembler car |
	assembler := CarAssembler using: FordFactory new.
	car := assembler assembleCar.
	"Now, draw the assembled car on the screen:"
	...

해법 변형(variation) 2번의 경우, CarAssembler는 그것의 팩토리 객체를 참조하는 인스턴스 변수를 가지지 않는다. 대신 클라이언트가 차를 조립하고 싶을 때 팩토리가 간단히 들어간다. 이는 assembleCar 메서드가 하나의 argument만 취하도록 변경시킨다.

CarAssembler>>assemblerCarUsing: aCarPartFactory
	| car |
	car = aCarPartFactory makeCar.
	car
		addEngine: aCarPartFactory makeEngine;
		addBody: aCarPartFactory makeBody;
		...
	^car

구현과 예제 코드

이제 추상 팩토리 패턴의 주요요소를 본인의 애플리케이션에 사용하기에 충분할 만큼 살펴보았다. 이번 단락에서는 추상 팩토리 주제에서 변형의 수를 이야기하고자 한다ㅡ특히 팩토리 객체를 구현하는 여러 방법을 논하고자 한다. 이 변형 중 일부는 [디자인 패턴]에서 다루고 있다; 우리는 스몰토크 특정적 다양성도 몇 가지 추가하였다. 여기서는 각 구현의 장단점을 지적하겠으나 특정 한 가지 해법을 옹호하려는 것은 아니다; 오히려 다른 모든 패턴들과 마찬가지로 다양한 방법으로 구현될 수 있음을 보이는 것이 목표다. 선택권은 특정 애플리케이션과 다른 객체들과의 상호작용으로 부과되는 추가 제약이나 개인의 선호도 및 미학에 따라 좌우된다. 크리스토퍼 알렉산더(Christopher Alexander)와 그 동료들은 패턴은 특정 문제에 대한 해법에 관련된 기본 견해와 개념을 제공하지만 “본인의 선호도와 지역적 상태에 조정함으로써 자신만의 방식으로” 실현시킬 수 있다고 주장했다 (Alexander et al., 1977, p. xiii).

자동차 팩토리에 대한 바닐라 구현

예제로 보여 준 애플리케이션에서 CarPartfactory는 모든 자동차 팩토리에 대한 인터페이스를 정의한다. 대안 팩토리는 CarPartFactory의 서브클래스로 정의되며, 각 대안 팩토리는 적절한 부품 생성 메서드를 오버라이드한다. 가장 간단히 말해 바닐라 구현에서 이러한 메서드 각각은 다음과 같이 인스턴스화하고 반환하기 위해 클래스를 하드코딩(hard-code)한다.

FordFactory>>makeEngine
	^FordEngine new
	
PorscheFactory>>makeEngine
	^PorscheEngine new

이 접근법은 각 부품에 대한 코드를 국지화(localize)한다. 물론 메서드를 국지화하는 것은 추후 변경의 국지화를 의미한다.

토요타가 포드로부터 엔진을 구매하기 시작한다고 가정하자 (자동차 산업에서는 실제로 이렇게 희한한 일이 발생하고 있기도 하다!). 새로운 유형의 엔진이란 ToyotaFactory>>makeEngine 메서드가 FordEngine인스턴스를 반환하도록 변화시킬 것을 암시한다; 모든 클라이언트 코드는 변경되지 않은 채 남는다.

Constant Method 해법

상기 진술한 구현은 가장 간단한 형태이지만 그 안에서 모든 팩토리 클래스의 각 부품에 맞는 각 메서드를 인스턴스화하기 위해 클래스를 하드코딩하였다. 이는 CarPartFactory와 그의 구체적 서브클래스에 make-Engine, makeBody 등의 메서드를 필요로 한다. 이 방법 대신 팩토리 메서드 (63) 패턴의 Constant Method 변형 중 하나를 적용시킬 수도 있다. 여기서는 각 부품 생성 메서드를 한 번만 정의하여, 클래스 객체를 쉽게 반환시키는 Constant Method를 이용해 (Beck, 1997) 인스턴스화하도록 각 팩토리 클래스가 부품 클래스의 "이름을 만들도록"" 할 것이다.

추상적 상위클래스에서 부품 생성 메서드를 팩토리 메서드로 재정의함으로써 시작한다:

CarPartFactory>>makeCar
	^self carClass new
	
CarPartFactory>>makeEngine
	^self engineClass new

CarPartFactory>>makeBody
	^self bodyClass new

그리고 난 후 각 팩토리 클래스에 Constant Method를 정의한다.

FordFactory>>carClass
	^FordCar
	
FordFactory>>engineClass
	^FordEngine
	
FordFactory>>bodyCalss
	^FordBody
	
PorscheFactory>>engineClass
	^PorscheEngine
	
PorscheFactory>>bodyClass
	^PorscheBody

이제 코드를 모듈화시켰으므로 새로운 자동차 팩토리 클래스가 정의되면 그에 상응하는 부품 클래스만 이름을 정하면 된다. 그러나 자동차 부품 예제에서 이것과 바닐라 구현 간 차이는 그다지 크지 않다.

부품 카탈로그 해법

상기 설명된 구현들의 경우, 자동차의 각 부품이 팩토리 클래스 내에서 고유의 메서드를 필요로 한다 (예: makeBody, makeEngine). 이는 팩토리 내에 메서드의 수를 과하게 생성하도록 만들기도 한다. 자동차에 새로운 유형의 부품, 즉 새로운 유형의 고급 오디오 시스템을 추가하려면 CarPartFactory와 그에 해당하는 모든 서브클래스에 새로운 makeDeluxeCDAudioSystem 메서드를 추가해야 할 것이며, 이를 통해 팩토리 클라이언트가 알아야 하는 인터페이스를 확장할 수 있을 것이다. [디자인 패턴] 편에서는 스몰토크에서 클래스는 일급 객체라는 사실을 이용하여 이 문제를 (DP90과 그 다음 문제) 해결하는 방법을 설명하고 있다. 자동차 부품의 클래스를 “부품 카탈로그”로 저장하고 부품마다 하나씩이 아니라 파라미터화된 유일한 부품 생성 메서드를 가진 CarPartFactory가 구현된다. 이러한 접근법은 CarPartFactory 클래스에 partCatalog인스턴스 변수를 추가하고 이것을 Dictionaryㅡ여기서 키는 부품 유형이 (Symbol과 같은) 되고 해당 값은 적절한 클래스가 된다ㅡ로서 초기화하는 과정을 수반한다. FordFactory 클래스의 partCatalog는 다음과 같을 것이다:


Key Value
#Car <the FordCar class object>
#Engine <the FornEngine class Object>
#body <the FordBody class object>
... ...

이 접근법을 가능하게 하는 새로운 CarPartFactory 클래스 정의와 메서드를 다음 페이지에 소개하고 있다:

Object subclass: #CarPartFactory
	instanceVariableNames: 'PartCatalog'
	classVariablesNames: ''
	poolDictionaries: ''
	
CarPartFactory class>>new
	^self basicNew initialize
	
CarPartFactory>>initialize 
	partCatalog := Dictionary  new

서브클래스는 고유의 부품 카탈로그 버전을 구축하기 위해 initialize 메서드를 다음과 같이 오버라이드할 것이다:

FordFactory>>initialize 
	super initialize.
	partCatalog
		at: #car put: Fordcar;
		at: #body put: FordBody;
		at: #engine put: FordEngine;
		...
	^self
	
PorscheFactory>>initialize 
	super initialize.
	partCatalog
		at: #car put: PorscheCar;
		at: #body put: PorscheBody;
		at: #engine put: PorscheEngine;
		...
	^self


이제 추상 상위클래스에 부품 생성을 위한 단일 메서드(single method)를 정의한다. 이는 부품 유형을 argument로 취한다.

CarPartFactory>>make: partType
	"Create a new part based on partType."
	| partClass |
	partClass := partCatalog at: partType ifAbsent: [^nil].
	^partClass new

이제 자동차 팩토리 클라이언트는 (예: CarAssembler) 이 단일 메시지를 이용해 모든 부품을 생성할 수 있다. 부품 생성 코드는 다음과 같은 모습이 아니라,

anAutoFactory make:   #engine.
anAutoFactory make:   #body.

다음과 같을 것이다.

anAutoFactory makeEngine.
anAutoFactory makeBody.

부품 카탈로그 접근법을 이용 시, CarPartFactory 계층구조의 구체적 클래스는 유일한 부품 카탈로그를 인스턴스화시키는 단일 메서드만 정의하면 된다. 그 외의 부품 생성 행위는 CarPartFactory 의 추상 클래스에 정의된다.

다른 부품 카탈로그를 위한 또 다른 구현

“부품 카탈로그” 접근법을 고려해 볼 때 다른 팩토리들을 구현하는 또 다른 방법이 있다. 여기서 다시 우리는 대안적 팩토리에 대비해 서브클래싱(subclassing)을 사용하기 때문에 클라이언트는 FordFactory, ToyotaFactory, PorscheFactory 중 하나를 선택해야 할 필요가 있다. 그러나 부품 카탈로그는 각 팩토리 클래스의 모든 인스턴스에 동일해야 하기 때문에 partCatalog를 클래스 인스턴스 변수로 정의하고, 클래스 변수를 적절히 초기화하기 위해 각 팩토리 클래스에 클래스 메서드를 코딩하며, 각 클래스마다 초기화 메서드를 한 번씩 호출할 수 있다. 우리는 추상 클래스를 다음과 같이 재정의한다:

object subclass: #carPartFactory
	instanceValiableNames: ''
	classVariablesNames: ''
	poolDictionaries: ''
	
CarpartFactory class
	instanceVariableNames: 'partCatalog'
	
CarPartFactory class>>make: partType
	"We moved this method; it's a class method now."
	| partClass |
	partClass := partCatalog at: partType ifAbsent: [^nil].
	^partClass new

이제 CarPartFactory의 구체적 서브클래스마다 하나의 클래스 인스턴스 변수가 있다. 모든 서브클래스가 내용을 공유하는 클래스 변수와 달리 각 서브클래스는 그 클래스 인스턴스 변수에 대해 고유의 private 버전을 가진다. 따라서 우리가 FordFactory의 partCatalog를 초기화하더라도 ToyotaFactory의 partCatalog에는 영향을 미치지 않는다. 각 클래스의 카탈로그를 다음과 같이 초기화할 수 있다:

CarPartFactory class>>new
	partCatalog isNil ifTrue: [self initialize].
	^self basicNew
	
CarPartFactory class>>initialize
	"Initialize the part catalog. This is now a class method"
	partCatalog := Dictionary new
	
FordFactory class>>initialize
	"Initialize the *local* part catalog"
	super initialize 
	partCatalog
		at: #car put: FordCar;
		at: #bodyt put: FordBody;
		at: #engine put: FordEngine;
		...

이제 남은 일은 인스턴스 변수 대신 클래스 인스턴스 변수를 사용하기 위해 부품 생성 인스턴스의 단일 메서드를 재정의하는 것이다. 이것은 동일한 서명(signature)으로 클래스 메서드를 호출할 뿐이다:

CarPartFactory>>make: partType
	"Create a new part based on partTyp"
	^self class make: partType

그럼에도 불구하고 페이지 DP 90에서 지적한 바와 같이 이 접근법은 제품군마다 새로운 구체적 팩토리 서브클래스를 필요로 한다 (Fords에 하나, Toyotas에 하나). 메시징 인터페이스를 단일 메서드로 줄이긴 했지만 팩토리 클라이언트들은 여전히 여러 개의 팩토리 클래스들 중 인스턴스화할 클래스를 선택해야 한다. 이는 약간의 유연성을 감소시킬 수 있다; 새로운 제품군이 생길 때 새로운 서브클래스를 추가하는 것은 확실히 단순한 작업이긴 하지만 좀 더 간편한 단일-클래스 구현을 구축할 수 있다. .

단일 팩토리 클래스

단일-클래스 해법은 하나의 팩토리 클래스만 구현하고, 인스턴스화하기 적절한 부품 클래스를 이용해 각 인스턴스의 부품 카탈로그를 초기화하는 것이다 (partCatalog를 인스턴스 변수로 하는 작업으로 돌아왔다). 다시 말해, CarPartFactory는 어떠한 서브클래스도 가지지 않으며, 클라이언트들이 항상 CarPartFactory 자체를 인스턴스화할 것이다. 이 접근법에서는 우리가 CarPartFactory 내에 단순한 클래스 메서드를 구현하고, 각 메서드마다 의미가 있는 팩토리 생성 이름을 가진다. 각 메서드는 팩토리의 유형마다 필요한 초기화를 수행한다. 이는 다음과 같은 형태를 띨 것이다:

CarPartFactory class>>fordFactory
	"Create and return a new Ford factory"
	| catalog |
	catalog := Dictionary new.
	catalog
		at: #car put: FordCar;
		at: #engine put: FordEngine;
		...
	^self new partCatalog: catalog
	
CarPartFactory class>>porscheFactory
	"Create and return a new Porsche factory"
	| catalog |
	catalog := Dictionary new.
	catalog
		at: #car put: PorscheCar;
		at: #engine put: Porscheengine;
		...
	^self new partCatalog: catalog

CarPartFactory 클라이언트가 포드 팩토리를 생성하고자 할 경우 다음과 같은 메시지 전송이 이루어진다:

catFactory := CarPartFactory fordFactory.

부품은 정확히 이전과 동일하게 생성된다:

catFactory make: #engine.

교묘한 단일-클래스 구현

우리는 (1) 일관된 명명규칙에 따라 우리의 모든 클래스를 정의하였고, (2) 스몰토크는 반영적 특성을 가진다,는 두 가지 사실을 이용하는 접근법을 통해 또 다른 단일-클래스 팩토리를 구현할 수 있다. 모든 부품 클래스 이름은 자동차 제작회사의 이름을 접두사로 하고 부품 이름을 접미사로 한다: FordEngine, ToyotaEngine, PorscheEngine; FordCar, ToyotaCar, PorscheCar. 이를 다음과 같이 이용할 수 있다:

CarPartFactory>>makeCar: manufacturersName
	"manufacturesName is a Symbol, such as
	#Ford, #Toyota, or #Porsche."
	| carClass |
	carClass := Smalltalk 
			at: (manufacturersName, #car) asSymbol
			ifAbsent: [^nil].
	^carClass new
	
carPartFactory>>makeEngine: manufacturersName
	| engineClass |
	engineClass := Smalltalk 
			at: (manufacturersName, #Engine) asSymbol
			ifAbsent: [^nil].
	^engineClass new

이것이 가능한 이유는 Smalltalk 사전에 엔트리로 모든 전역변수가 참조되고 있으며, 클래스는 동일한 이름의 global에 의해 참조되기 때문이다. 따라서 Smalltalk at: #FordEngine이라는 표현식(expression)은 FordEngine 클래스 객체를 검색한다.

이 접근법을 이용해 자동차 팩토리는 항상 CarPartFactory의 인스턴스가 될 것이다. 자동차 부품 생성은 다음 형식을 취한다 (여기서 carCompany는 사용자로부터 얻은 Symbol이다):

carFactory := CarPartFactory new.
	car := carFactory makeCar: carCompany.
	car
		addEngine: (carFactory makeEngine: carCompany);
		...

그러나 이 접근법을 이용 시 단점이 있다. 정적 검사(static inspection)이나 동적 런타임 추적으로부터 코드를 이해할 경우 정체성(identity)이 대충 구성된 클래스로 메시지를 전송하고 참조하기 때문에 이해하기가 더 힘들다는 점이다. 예를 들어, FordCar 클래스를 참조하는 메서드를 모두 찾기 위해 스몰토크 개발환경의 도구를 사용할 경우, makeCar: 메서드는 그 결과 메서드 목록에 포함되지 않을 것이다. makeCar:는 모든 자동차 클래스를 암묵적으로 참조하지만 코드에 명시적으로 나타내지는 않는다.

알려진 스몰토크 사용예

UILookPolicy

비주얼 웍스에서 UIBuilder는 위젯의 특징들을 바탕으로 윈도우를 구성하는 책임을 가진다 (다른 기능들도 있지만). 각 위젯마다 UIBuilder는 실제 위젯 인스턴스 생성을 수행하기 위해 그와 관련된 UILookPolicy를 요청한다. UILookPolicy의 서로 다른 서브클래스가 최근 선택된 룩앤필(look and feel)에 따라 (윈도우, OS/2, Motif,Macintosh, 또는 기본 스몰토크 모양) 서로 다른 위젯을 인스턴스화시키기 위해 이러한 생성 메시지를 다형적으로 구현한다. 하지만 UIBuilder는 그것이 다루는 모양 정책 객체(look policy object)가 무엇인지에 대한 지식이 없다. 다시 말해 Win3LookPolicy, CUALookPolicy 또는 다른 정책 객체와 연결되어 있는지 알지 못하거나 신경쓰지 않는다는 것이다; 단지 추상적 UILookPolicy 프로토콜에 정의된 일반적 위젯 생성 메시지를 전송할 뿐이다. 따라서 모양 정책 객체는 위젯에 대한 추상 팩토리 역할을 하는 것이다.

Constant Method 이용하기

Constant 메서드를 사용해 추상 팩토리가 구현되는 방법은 이미 소개한 바 있다. UILookPolicy에 의한 사용 예제도 제시되었다 (팩토리 메서드에서도 이 예제를 언급하였다). 예를 들어, UILookPolicy에 슬라이더 위젯을 생성하는 메서드가 있다고 가정하자.

slider: spec into: builder
	...
	component := self sliderClass model: model.
	...

model: 메시지는 sliderClass가 반환시킨 클래스를 인스턴스화한다. UILookPolicy는 sliderClass의 기본(default) 버전을 정의하고, 그의 구체적 서브클래스 일부는 슬라이드를 서로 다른 모양으로 생성하기 위해 그것을 오버라이드한다:

UILookPolicy>>sliderClass
	^SliderView
	
MacLookPolicy>>sliderClass
	^MacSliderView
	
Win3LookPolicy>>sliderClass
	^Win3SliderView

관련 패턴

Builder

추상 팩토리는 Builder(47) 패턴과 밀접하게 관련되어 있다. 주요 차이점은 전체적인 제품 조립을 누가 책임지느냐이다. 추상 팩토리에서는 함께 작업하거나 함께 작동하도록 보장된 부품을 팩토리가 반환한다. 하지만 하나의 최종 제품으로 조립하는 일은 팩토리 클라이언트가 한다. 팩토리로 전송되는 모든 메시지는 전체적인 최종 제품의 서브컴포넌트 또는 새로운 부품의 결과를 가져오고ㅡ즉 팩토리 내 모든 부품 생성 메서드는 컴포넌트 부품을 반환한다는 의미ㅡ팩토리의 클라이언트는 이 부품을 각각 생성되는 제품에 추가한다. 반면, Builder는 제품이 조립되는 동안 제품을 유지할 수 있는 고유의 내부 상태를 가진다. Builder는 "컴포넌트 A를 추가한다," "컴포넌트 B를 추가한다,"라는 요청을 듣게 되면 이 서브컴포넌트를 캡슐화된 Product로 추가하는 것은 Builder의 일이다. “컴포넌트 X를 추가하라”라는 메시지가 전송될 때마다 Builder는 아무 것도 반환하지 않는다. Builder의 클라이언트가 하위부품의 추가작업을 완료하면 클라이언트가 Builder에게 "최종 제품을 보여달라,"고 말한다. 그리고 나서야 Builder는 최종 제품을 반환한다.

Factory Method

팩토리 객체는 어떤 클래스를 인스턴스화할지 결정하기 위해 팩토리 메서드(63) 패턴의 Constant 메서드 변수를 호출할 수 있음을 앞에서 나타낸 바 있다. 그리고 팩토리 메서드는 실제적으로 추상 팩토리의 경쟁자라고 볼 수 있지만, 두 패턴은 어떻게 보면 서로 구조적으로 정반대에 해당한다. 추상 팩토리의 바닐라 구현 본문에서 우리는 다수의 부품군ㅡ제품군마다 하나의 클래스ㅡ를 고려하기 위해 다수의 구체적 팩토리 클래스를 정의하고 있다. 그리고 난 후 단일 애플리케이션 객체가 동일한 코드를 이용해 어떤 제품군으로부터든 부품을 인스턴스화시킬 수 있도록 하였다. 애플리케이션은 바람직한 부품군과 관련된 팩토리 클래스를 인스턴스화시킬 뿐이다 (포드 부품을 원하면 FordFactory를 인스턴스화하는 것이다). 구조 다이어그램에서 단일 클라이언트는 팩토리 클래스의 하위계층구조로부터 하나의 팩토리 객체를 가리킨다는 점을 명심한다.

팩토리 메서드 접근법을 이용할 경우 팩토리 클래스의 전체 계층구조를 정의하는 작업을 피할 수 있다. 하지만 그 대신 애플리케이션 클래스의 계층구조가 필요할 것이다. 모든 구체적 서브클래스에 대한 엄청난 양의 행위를 정의하면서도 팩토리 메서드의 정의는 서브클래스로 미루는 추상 애플리케이션 클래스가 필요할 것이다. 각 서브클래스는 그에 따라 팩토리 메서드를 오버라이드할 것이다: 포드 애플리케이션 클래스는 그 팩토리 메서드가 포드 부품을 반환하도록 시킬 것이며, 토요타와 관련된 애플리케이션 클래스는 토요타 부품 객체를 생성 및 반환하는 팩토리 메서드가 있을 것이다.

Notes

  1. 추상 팩토리 패턴 이후에 나오는 Builder (47) 패턴을 읽어보길 권한다; 두 패턴은 밀접한 관계가 있으며 여러 문제를 공유한다.
  2. 이 점은 팩토리의 부품 자체가 복잡하게 구성된 부품일 경우에도 마찬가지다. 예를 들어, 팩토리는 여러 개의 하위컴포넌트 위젯들이 통합된 복합 판유리와 같이 미리 조립된 부품을 반환할 수도 있는 것이다. 그럼에도 불구하고, 팩토리 클라이언트에게는 이것이 창문처럼 좀 더 복잡한 제품으로 통합될 수 있는 하나의 부품으로 간주된다.
  3. 언뜻 보기엔 조립 태스크의 최종 제품인 새 Car를 생성하는 factory makeCar를 말함으로써 자동차 조립 공정을 시작하기가 혼란스러워 보일 수도 있다. 사실 이 방법은 복합 객체를 구축하는 전형적인 방법이다. 비주얼 스몰토크를 예로 들자면, Menu의 구성을 시작할 때 우리는 Menu new라고 말한다. 이 시점에 우리가 가진 것은 메뉴의 셸(shell)에 불과하다; MenuItems라는 하위컴포넌트를 추가하기 전까지는 메뉴의 기능을 하지 못한다. 이와 비슷하게, factory makeCar는 엔진이나 차체와 같이 우리가 추가해야 하는 컴포넌트로 Car의 빈 셸을 반환한다.