DesignPatternSmalltalkCompanion:FactoryMethod

From 흡혈양파의 번역工房
Jump to: navigation, search

FACTORY METHOD (DP 107)

의도

객체를 생성하는 인터페이스를 정의하지만 어떤 클래스를 인스턴스화할지는 서브클래스가 결정한다. Factory Method 패턴에서는 클래스의 인스턴스 생성 시점을 서브클래스로 미룬다.

구조

Dpsc chapter03 FactoryMethod 01.png

논의

팩토리 메서드는 충분히 간단하면서도 객체지향 프로그래밍의 아름다움을 보여준다. 이는 잠재적으로 복잡해질 수 있는 문제에 멋진 해법을 제공하기 위해 다형성, 동적 바인딩, 정보 은닉을 활용한다. 또한 프레임워크의 생성과 그에 따른 구체적 인스턴스화를 촉진하기도 한다.

프레임워크는 하위계층구조 집합을 정의하고, 이 클래스의 인스턴스들이 애플리케이션(예: 문서 편집기)의 특정 클래스 핵심(core) 또는 재사용가능한 특정 타입의 서비스, 기능, 애플리케이션 컴포넌트를 생성하기 위해 어떻게 함께 작동하는지 정의한다. 참여하는 각 계층구조 가지의 뿌리(root)에 있는 추상 클래스는 프레임워크에서 구현된 보편적 오퍼레이션을 정의하며, 그에 따라 프레임워크 내 객체가 어떻게 행동하고 상호작용 하는지를 설명한다. 프레임워크의 실제 인스턴스화는 이러한 뿌리 클래스의 구체적 하위클래스의 인스턴스들로 구성된다.

일반적인 프레임워크에서는 한 계층구조(이것의 최상위 클래스는 Principal이라 하자)의 객체들은 다른 계층구조(이것의 최상위 클래스는 Associate라고 하자)의 인스턴스와 연관되어 있다. 때때로 Principal 트리의 서로 다른 클래스는 Associate의 서로 다른 서브클래스와 협력해야 할 필요가 있다. 예를 들어, Principal의 서브클래스인 Principal2가 Associate2의 인스턴스와 쌍을 이루어야 할 필요가 있을 수도 있으며, Principal3 객체가 Associate3 인스턴스와 협력해야 하는 경우도 있는 것이다. 팩토리 메서드 패턴은 Principal 계층구조의 각 클래스가 어떤 클래스의 인스턴스를 만들어 사용할 지 결정하는 어떤 메서드를 작성함으로서 클래스 아키텍처를 쉽게 만들 수 있게 한다.

늦은 설계 결정은 인스턴스화 임무를 자신의 메서드로 깔끔하게 분리시킴으로써 전체 코드를 단순화시킨다. 이는 잠재적으로 발생하는 수 많은 코드의 중복을 피하게 해준다. Principal에 임의의 복잡한 메서드가 있다고 하자. 그런데 코드의 군데군데에서 그 Principal 클래스에 협업하는 Associate 객체를 생성하는 코드가 있다고 하자; 팩토리 메서드 패턴을 사용하면 Associate 객체 인스턴스화를 위한 별도의 메서드를 작성하여 추상화할 수 있다. 팩토리 메서드 패턴을 사용하지 않는다면 ㅡ즉 인스턴스화할 클래스 이름을 하드코딩할 경우ㅡ 추상 클래스에 다음과 같은 메서드가 있을 것이다.

Principal>>start
	"Without Factory Method."
	...
	"Associate is an abstract class. Subclasses must
	override this method and change the following line!"
	associate := Associate new.
	associate message1.
	...

이제 관련된 객체를 인스턴스화하는 한 라인을 오버라이드 할 경우에만 각 Principal 서브클래스가 전체 start 메서드를 복사하여 구현해야 할 것이다. 예를 들어, 인Associate2의 인스턴스와 쌍이 되어야 하는 인스턴스를 가진 Principal2에서 우리는 다음과 같이 start를 구현할 수밖에 없을 것이다.

Principal2>>start
	"This method is copied from Principal>>start except
	here instantiate Associate2 for my associate object."
	...
	associate := Associate2 new.
	associate message1.
	...

팩토리 메서드 패턴은 더 효과적이고 수월하며 확장가능한 해법을 제공한다. start 메서드의 코드 중에서 Principal 계층구조의 모든 클래스에 동일한 부분을 남겨두고, 서브클래스마다 다른 코드 조각을 뽑아내 그 서브클래스에 둘 수 있다. 이 코드 조각은 적절한 Associate 클래스를 인스턴스화시키는 코드에 불과하다; 이 코드 조각은 별도의 메서드로 옮겨진다. 그 메소드를 팩토리 메서드라고 부른다. 이제 start는 다음과 같은 모습일 것이다:

Principal>>start
	"With Factory Method."
	...
	associate := self createAssociate.
	associate message1.

관련 객체의 생성에 대한 책임은 메인 메서드로부터 독립적인 createAssociate 팩토리 메서드로 옮겨간다. 그 결과, 각 서브클래스는 start 메서드를 재정의할 필요 없이 createAssociate를 오버라이드하기만 하면 된다. Principal과 그의 모든 서브클래스는 고유의 createAssociate 을 재정의함으로써 그들이 필요로 하는 Associate 가 무엇이 되었든 마음대로 인스턴스화할 수 있다.

Principal>>createAssociate
	self subclassResponsibility

Principal2>>createAssociate
	^Associate2 new

Principal3>>createAssociate
	^Associate3 new

특정 Principal 클래스를 변경하기 위해 Associate 클래스를 변경할 필요가 있을 경우 그 한 부분만 변경이 가능하다는 또 다른 장점도 있다. 사실 Principal 클래스에는 Associate 서브클래스를 참조하는 다수의 메서드가 있다. 이 메서드들이 클래스 이름을 하드코딩하면 이름을 변경 시 모든 메서드마다 참조를 추적하는 과정이 수반된다. 이와 반대로, 팩토리 메서드를 사용 시 그 단일 메서드만 변경하면 되는 것이다. Principal에 Associate를 참조하는 메서드가 하나밖에 없는 경우에도 팩토리메서드패턴은 여전히 유용하다. Associate 클래스를 참조하는 부분을 찾는 수고를 하는 대신, 팩토리 메서드를 찾기만 하면 된다.[1]

템플릿 메서드를 사용한 구현

팩토리 메서드는 항상 템플릿 메서드 (355) 패턴을 이용해 구현된다. 템플릿 메서드 패턴을 다룬 절에서는 어떻게 그 패턴이 다양한 메시지가 몇 안 되는 작은 메시지가 같은 수신자에게 보내짐으로써 구현되도록 관여하는지 알아볼 것이다. 더 넓은 템플릿 메서드는 상위클래스에서 정의되며, 그 클래스 또는 그의 서브클래스에서는 더 좁은 원시 메서드가 구현된다. 템플릿 메서드는 자신(self)에게 메시지를 전송함으로써 원시 메서드(primitive method)를 호출한다. 팩토리 메서드 패턴의 맥락에서 팩토리 메서드는 템플릿 메서드 패턴에서의 원시 메서드에 해당한다. 여기서 설명하는 예제에서는 start가 템플릿 메서드, createAssociate가 원시 메서드가 된다.

문서처리 프레임워크

몇 가지 프레임워크에 적용된 팩토리 메서드를 살펴보자: [디자인 패턴]에서 설명된 예제와 함께 스몰토크 프레임워크의 할아버지격인 모델-뷰-컨트롤러(MVC)를 살펴보고자 한다.   단순한 문서처리 애플리케이션을 위한 프레임워크는 [디자인 패턴] 편 DP 107페이지에 소개하고 있다. 그 프레임워크에서 도출한 예제를 사용할 것이다. 추상 클래스 Editor ([디자인 패턴]편에서는 Application이라 부름)와 Document는 핵심 추상화(key abstraction)와 편집기와 문서 간 상호작용을 담고 있다. 이 추상 클래스들의 구체적 서브클래스로 인스턴스를 만들어 그림 편집기나 텍스트 편집기를 생성할 수 있다.

특정 Editor 서브클래스는 특정 Document 서브클래스와 짝을 이루어야 한다. 이 프레임워크의 그림 편집기는 그림 문서에 접근할 수 있고, 텍스트 편집기는 텍스트 파일의 편집을 할 수 있다. 그림 편집기 애플리케이션의 경우, DrawingEditor가 Editor의 서브클래스로서 정의되고 DrawingDocument는 Document의 서브클래스로서 정의된다. 그리고 텍스트 편집기 애플리케이션을 위해서, TextEditor와 TextDocument 서브클래스가 있다.

프레임워크의 정의는 Editor에 단일 팩토리 메서드를 포함하는데 이는 다음과 같이 정의되어야 한다:

Editor>>createDocument
	"Factory method to instantiate my associated
	Document class. Subclasses must override."
	self subclassResponsibility

Editor의 각 구체적 클래스는 문서생성 팩토리 메서드를 오버라이드하여 서로 다른 Document 서브클래스를 인스턴스화한다. 따라서 추상 클래스에서 계층구조가 어떻게 상호작용하는지에 대한 일반지식은 있지만 애플리케이션 특정적 지식은 구체적 서브클래스로 미룬다. 긍정적인 부수효과로는 서브클래스의 코드를 최소화시킬 수 있다는 점을 들 수 있다. 이번 예제에서는 다른 많은 프레임워크에서와 마찬가지로, 서브클래스의 행위 중 다수가 상속되면서 문서-생성 행위를 다형적으로 오버라이드한다. 아래에는 우리 예제의 문서처리 프레임워크에 대한 구조 다이어그램을 나타내고 있으며, 팩토리 메서드 패턴을 구현하는 예제코드도 포함하고 있다.

Dpsc chapter03 FactoryMethod 02.png

새로운 텍스트 편집기 애플리케이션을 생성하고 싶다면 TextEditor를 인스턴스화한다. TextEditor가 문서를 생성할 필요가 있을 경우 newDcoument 메서드ㅡEditor로부터 상속된ㅡ가 호출된다. 이 메서드는 다음 문장을 포함한다.

doc := self createDocument.

createDocument는 Editor의 구체적 서브클래스에 의해 오버라이드된 팩토리 메서드이다. TextEditor>>createDocument는 호출되면서 끝이 나고, 새로운 TextDocument를 생성한다. 이와 비슷하게, DrawingEditor의 createDocument 메서드가 실행 시 새로운 DrawingDocument를 생성한다. 따라서 팩토리 메서드는 일반화된 메커니즘에 의해 특정 애플리케이션의 요구사항에 맞는 관계를 만들어낸다ㅡTextEditor->TextDocument과 DrawingEditor->DrawingDocument.

Model-View-Controller에서 팩토리 메서드

앞에서 보았듯 팩토리 메서드는 서로 관련된 두 개의 계층구조가 있는 상황에서 유용하다. 전형적인 예는 스몰토크-80 MVC 프레임워크에서 제공하고 있는데, 여기서 각 뷰(윈도우 또는 화면 위젯)는 , 뷰에 대한 (마우스나 키보드를 통한) 사용자 상호작용 취급하는 컨트롤러를 갖는다. 뷰의 유형마다 다른 유형의 컨트롤러가 필요하다. 예를 들어, TextView는 TextController를, BitView는 BitEditor 컨트롤러를 필요로 한다. 그들의 관계를 보여주는 두 개의 계층구조 중 일부를 나타내고자 한다 (들여 쓴 자리는 하위계층구조에서 상대적 위치를 나타내며, 중간에 위치한 추상 클래스 일부는 그림에서 제외하였다).

View Class Corresponding Controller Class
View Controller
ComposedTextView
ParagraphEditor
TextEditorView
TextEditorController
InputFieldView
InputBoxController
TextView
TextController
DebuggerTextView
DebuggerController
ListView
ListController
ChangeListView
ChangeListController
SelectionInListView
SelectionInLIstController
BitView
BitEditor
ColorBitView
BitEditor(inherited)
BooleanWidgetVIew
WidgetController
ActionButton
WidgetController(inherited)

확실히 많은 클래스가 포함되어 있으며 새 클래스를 추가해야 할 수도 있다. 따라서 특정 View 클래스와 특정 Controller 클래스를 연관시킬 수 있는 쉬우면서도 확장가능한 방법이 필요하다.

비주얼웍스의 설계자들은 이 문제에 해결하는 여러 가지 방법을 선택할 수 있었다. 그들은 클래스 연관관계의 등록소를 도입할 수도 있었다. 스몰토크에서는 Dictionary가 등록소의 역할을 할 수 있으므로, View 클래스를 Dictionary의 키로 Controller 클래스를 Dictionary의 값으로 하는 구현하는 구현도 가능했다. 이 해법은 관리의 문제가 있다. 새로운 타입의 위젯을 위해 새로운 View 서브클래스가 정의한다면, 이 View는 새로운 Controller를 필요로 하게 될 것이다. 클래스들을 정의하고 나면, 잊지말고 등록소를 갱신하여 이 클래스들을 제대로 등록해야 한다. 또한 모든 View 서브클래스는ㅡ심지어 상위클래스와 동일한 컨트롤러 타입을 사용하길 원하는 서브클래스도ㅡ등록소에 일일이 등록해야 한다.

그런 방법을 쓰는 대신 비주얼웍스는 팩토리 메서드 패턴을 사용했다. View가 인스턴스화되고 나면 그것의 컨트롤러는 다음과 같이 생성된다. 클라이언트가 getController 메시지로 View의 컨트롤러를 처음으로 요청하면 게으른 초기화(lazy initialization)로 그 생성을 처리한다 (이 코드는 비주얼웍스 이미지로부터 직접 얻은 것이다).

View>>getController
	"Answer the receiver's current controller. If the
	receiver's controller is nil (the default case) , an
	initialized instance of the receiver's default
	controller is installed and returned."

	controller == nil
		ifTrue: [self setController: self defaultController].
	^controller

setController: 에서 뷰의 controller 인스턴스 변수에는 self defaultController의 실행 결과로 지정된다. defaultController는 팩토리 메서드이다. 뿌리 뷰 클래스에 다음과 같이 구현되어 있다:

View>>defaultController
	^self defaultControllerClass new

앞서 언급한 바닐라 팩토리 메서드 패턴에서는 defaultcontroller가 독립된 팩토리 메서드가 될 수도 있다; 적절한 클래스를 명시하고 인스턴스화를 할 수도 있다. 예를 들어, View와 그 서브클래스에 대한 구현 전략이 될 수도 있는 것이다:

View>>defaultController
	^Controller new

TextView>>defaultController
	^TextController new

그러나 비주얼웍스는 이 패턴을 약간 변경하여 추가적인 간접 수준을 야기하였다. 루트 뷰 클래스에 defaultController가 한 번 정의되었고, 이것은 defaultControllerClass라는 두 번째 메시지를 전송하여 반환된 클래스를 인스턴스화시킨다. defaultControllerClass 메서드는 인스턴스화되는 클래스를 답할 뿐이다. 이제 서브클래스는 관련 Controller 클래스의 이름을 답하는 defaultControllerClass 메서드만 오버라이드하면 된다.

defaultControllerClass의 기본 구현은 다음과 같다:

View>>defaultControllerClass
	"Answer the class of the default controller for
	the receiver."
	"Subclasses should redefine defaultControllerClass if
	the class of the default controller is not Controller."
	^Controller

이 메서드는 기본 컨트롤러가 아닌 컨트롤러를 필요로 하는 View 서브클래스에 의해 오버라이드된다. 아래에 예를 소개하겠다:

TextView>>defaultControllerClass
	^TextController

BitView>>defaultControllerClass
	^BitEditor

상위클래스와 동일한 타입의 컨트롤러를 사용하길 원하는 클래스는 defaultControllerclass를 그대로 상속한다.

이 해법에서 첫 번째 메서드는 "기본 인스턴스 얻기" 팩토리 메서드(defaultController)로서, 클래스 View에서 한 번 정의된 후 "기본 인스턴스의 클래스를 얻기" 라는 내장된 원시 메서드를 (defaultControllerClass) 오버라이드할 수 있는 모든 서브클래스에 의해 상속되는 하나의 템플릿 메서드이기도 하다는 점을 명심한다. 이 메서드는 본질상 하나의 상수, 즉 너무나 자연스럽게 하나의 클래스 객체를 돌려보낸다 ㅡ Beck (1997)는 이것을 Constant Method라고 이름을 붙였다. 팩토리 매소드 패턴의 이러한 변형은 템플릿 메서드 패턴과 Constant Method 패턴을 사용하여 실제 인스턴스의 인스턴스화로부터 클래스 명세를 분리한다.[2]

구현

3가지 기본방식

스몰토크 환경에서 3가지 기본 방식으로 구현되는 팩토리 메서드를 살펴보았다. 1. [디자인 패턴] 편에서와 같이 팩토리 메서드는 협력자(collaborator) 클래스를 명시하고 인스턴스화한다.

Application>>mainMethod
	...
	collaborator := self defaultCollaborator.
	...

Application>>defaultCollaborator
	^Collaborator new

2. 비주얼웍스의 View-Controller 에제와 같이, 팩토리 메서드는 2가지 방법으로 나눌 수 있다: "기본 인스턴스 얻기" 팩토리 메서드와 "기본 클래스 얻기" 상수 메서드가 그것이다.

Application>>mainMethod
	...
	collaborator := self defaultCollaborator.
	...

Application>>defaultCollaborator
	"This is the factory method; the instance is
	created here, but based on the class retrieved
	from a secondary Constant Method"
	^self defaultCollaboratorClass new

Application>>defaultCollaboratorClass
	"This is the Constant Method. It specifies the
	class to instantiate to work with 'Application'
	instances"
	^Collaborator

이것은 스몰토크의 클래스가 런타임 객체이기 때문에 가능한 해법 중 하나이다. 클래스는 메시지에 응답할 수 있기 때문에 defaultCollaboratorClass는 단지 클래스 객체를 반환하고, defaultCollaborator가 new 메시지를 보내도록 놔둔다. C++의 경우에는 클래스 객체가 없기 때문에 이런 방식으로 패턴을 구현할 수 없을 것이다; C++에서 팩토리 메서드는 인스턴스화할 클래스의 이름을 답하는 식으로는 할 수 없고, 새 인스턴스를 반환해야만 한다.

3. 3번째는 작은 변종 해법은, 인스턴스화가 메인 메서드에 발생하고, 보조적인 상수 메서드(auxiliary Constant Method)가 인스턴스화할 클래스를 반환한다 (변종 2와 마찬가지로):

Application>>mainMethod
	...
	collaborator := self defaultCollaboratorClass new.
	...

Application>>defaultCollaboratorClass
	"Constant Method; return the collaborator class"
	^Collaborator

세 번째 버전은 팩토리 메서드 패턴이라고 보기는 힘들다; 그 이유는 협력자 객체를 생성하는 별도의 메서드가 없기 때문이다. 대신 인스턴스화할 클래스만 메인 메서드로부터 추상화되지만, 클래스의 인스턴스화는 그 메인 메서드 내에서 발생한다. 이 접근법을 논의에 포함시키는 이유는 스몰토크에서 광범위하게 사용되고 있기 때문이다; 게다가 팩토리 메서드의 기본 의도를 공유한다:
"객체를 생성하는 인터페이스를 정의하지만 어떤 클래스를 인스턴스화할지는 서브클래스가 결정한다."

사실상 전체적으로 봤을 때 상수 메서드의 방식들은ㅡ버전 2와 3ㅡ스몰토크 기본 클래스에서 팩토리 메서드의 의도를 성취하는 방법을 너무나도 압도적으로 보여준다. 흥미로운 점이 있다면, 비주얼 스몰토크(Visual Smalltalk)에서는 버전 3이 가장 많이 사용되는 반면 비주얼웍스에서는 버전 2가 가장 많이 사용된다는 점이다.

클래스 인스턴스 변수를 이용한 해법

기본 패턴에서 이 변형은 인스턴스화시킬 협력자 클래스를 결정하기 위해 하나의 클래스 인스턴스 변수를 이용하여 하위계층구조에 속한 모든 클래스에 대해 당신이 단일 팩토리 메서드를 정의할 수 있도록 해준다. 이를 Editor 예제에 적용시켜보자. Editor 클래스 정의에 다음을 추가하면서 시작한다:

Editor class instanceVariableNames: 'documentClass'

하위계층구조 내의 모든 클래스가 문서를 생성할 때 사용할 팩토리 메서드를 먼저 정의한다:

Editor>>createDocument
	^self class documentClass new

다음으로 documentClass 클래스 메서드를 정의하는데, 이 메서드는 인스턴스화할 클래스를 반환한다. 다시 언급하지만, 이는 상위클래스에서 한 번만 정의된다.

Editor class>>documentClass
	"Return my class instance variable that specifies
	the document class to instantiate for my instances."
	^documentClass

이제 Editor 계층구조 내의 각 서브클래스는 사용하고자 하는 문서 클래스로 자신의 documentClass 변수를 초기화한다:

DrawingEditor class>>initialize
	documentClass := DrawingDocument

TextEditor class>>initialize
	documentClass := TextDocument

이제 각 서브클래스에 단일 메서드ㅡ초기화ㅡ를 정의하면 된다. 나머지 팩토리 메서드 구현은 Editor로부터 상속된다. 또한 메서드로 인스턴스화할 클래스를 하드코딩을 할 필요 없이 클래스의 documentClass 변수를 변경함으로써 그때그때 클래스를 변경할 수 있다.

예제코드

Builder (47) 패턴에서 사용되었던 예제 하나로 돌아 가 보자. 추상 클래스 CarBuilder와 그에 해당하는 서브클래스 FordBuilder, ToyotaBuilder, PorscheBuilder가 있다. 각 클래스는 동일한 빌딩 프로토콜에 응답하지만 각각의 Builder 클래스는 서로 다른 부품 클래스를 인스턴스화했었다. 예를 들어 클라이언트가 4개 실린더 엔진을 자동차에 설치하고자 할 경우, add4CylinderEngine를 Builder로 전송한다. 그리고 Builder는 저마다 다른 방식으로 add4CylinderEngine를 구현했었다; FordBuilder는 Ford4CylinderEngine의 인스턴스를, ToyotaBuilder는 Toyota4CylinderEngine을 인스턴스화하는 것이다.

구현에 관한 한 가지 질문은, 이 Builder는 add4CylinderEngine 메서드로 인스턴스화할 클래스를 어떻게 명시하는지에 관한 질문이다. 이는 전반적으로 모든 자동차 부품에 적용된다. Builder 패턴에서는 각 Builder가 클래스 이름을 하드코딩하는 것을 보였다. 팩토리 메서드는 이에 대안적 접근법을 제시한다.

Builder 패턴에서 add4CylinderEngine 메서드는 다음과 같은 모습이었다:

CarBuilder>>add4CylinderEngine
	"Do nothing. Subclasses will override."

FordBuilder>>add4CylinderEngine
	self car addEngine: Ford4CylinderEngine new

ToyotaBuilder>>add4CylinderEngine
	self car addEngine: Toyota4CylinderEngine new

위의 메서드를 대신해 팩토리 메서드를 이용하여 Builder의 구체적 클래스에 아래의 메서드를 구현할 수 있다:

FordBuilder>>add4CylinderEngine
	"Add a 4-cylinder engine; it is created by
	invoking a factory method."
	self car addEngine: self fourCylinderEngine

ToyotaBuilder>>add4CylinderEngine
	self car addEngine: self fourCylinderEngine

구현을 완료하기 위해선 빌더 특정적 팩토리 메서드를 구현할 필요가 있다:[3]

FordBuilder>>fourCylinderEngine
	"The Ford 4-cylinder engine factory method."
	^Ford4CylinderEngine new

ToyotaBuilder>>fourCylinderEngine
	^Toyota4CylinderEngine new

이제 리팩토링할 수 있는 확실한 기회가 있다. add4CylinderEngine 메서드는 모든 CarBuilder 서브클래스에서 동일하기 때문에 추상 상위클래스에서 메서드를 한 번 정의한 후 모든 서브클래스로부터 완전히 제거할 수 있다. CarBuilder의 메서드는 위에서 정의한 것과 똑같은 모습을 하고 있지만 한 곳에서만 나타난다.

CarBuilder>>add4CylinderEngine
	self car addEngine: self fourCylinderEngine

팩토리 메서드 패턴을 호출함으로써 사실상 더 깔끔하고 쉽게 관리할 수 있는 구현으로 마무리할 수 있다. add4CylinderEngine의 행위를 변경하고 싶을 때 다수의 서브클래스가 아니라 한 곳 바꾸면 되는 것이다.

알려진 스몰토크 사용예

팩토리 메서드는 스몰토크에서 가장 널리 사용되는 패턴 중 하나이다. MVC의 예제는 앞에서 살펴보았기 때문에 다른 예제를 몇 가지 들고자 한다. 이 모든 예제는 상수 메서드 패턴의 변형 중 하나를 사용한다.

비주얼 스몰토크의 윈도우 정책

비주얼 스몰토크에서 각 TopPane(애프리케이션 윈도우를 표현)은 WindowPolicy의 서브클래스들 중 한 서브클래스의 인스턴스와 관련이 있다. WindowPolicy 객체들은 애플리케이션 윈도우의 메뉴 바를 구성하도록 돕는다. 서브클래스는 각자 다른 풀다운 메뉴를 메뉴 바에 추가한다. StandardWindowPolicy 객체는 FileEdit 메뉴를 추가하고, SmalltalkWindowPolicy는 Smalltalk 풀다운을 추가하며, NomenusWindowPolicy는 따로 설명하지 않아도 알 것이다. TopPane이 메뉴 바를 만들기 시작하면 먼저 그 메뉴를 추가하기 위해 윈도우 정책 객체를 요청한다 (그 외 애플리케이션 특정적 풀다운 메뉴는 추후에 추가된다). TopPane은 클라이언트에 의해 WindowPolicy를 이용해 명시적으로 구성될 수 있지만, 그렇지 않을 경우 주인(owner) 애플리케이션에게 (일반적인 경우) 윈도우 정책 클래스를 요청하고 다음과 같이 인스턴스화한다.

	owner windowPolicyClass new

ViewManager은 StandardWindowPolicy를 반환하는 기본 메서드 windowPolicyClass를 정의한다. ViewManager 서브클래스가 다른 정책 객체를 사용해야 할 경우 서브클래스는 이 메서드를 오버라이드한다.

일부 ViewManager 서브클래스는 현재의 맥락에 따라 어떤 정책 클래스를 사용할지 런타임 시 결정하는도록 windowPolicyClass를 오버라이드한다. 예를 들어, Browser>>windowPolicyClass는 현재 실행 중인 이미지가 스몰토크 개발 환경일 경우 어떤 클래스를 답하고, 그렇지 않을 경우 다른 클래스를 반환한다.

비주얼웍스의 모양 정책

팩토리 메서드의 또 다른 예는 비주얼웍스의 UILookPolicy에서 찾아볼 수 있다. 라디오 버튼을 예로 하면, 모양 정책 객체는 정책 객체의 클래스에 따라 라디오 버튼의 모양을 생성하도록 요청받는다. 예를 들어, Win3Lookpolicy 인스턴스는 윈도우 3.x 모양으로 라디오 버튼을 생성하는 반면 MotifLookPolicy 객체는 다이아몬드 모양의 모티프 스타일 라디오 버튼을 생성한다.

여기 UILookpolicy 추상 클래스에 라디오 버튼을 생성하는 메서드를 소개하겠다:

UILookPolicy>>radioButton: spec into: builder
	...
	component := self radioButtonClass model: model.
	...

radioButtonClass는 클래스 객체를 반환하고, model: 메서드는 그 클래스로 인스턴스를 만든다. UILookPolicy 계층구조에서 각 클래스는 고유의 radioButtonClass 버전을 정의한다:

UILookPolicy>>radioButtonClass
	^DefaultLookRadioButtonView

Win3LookPolicy>>radioButtonClass
	^Win3RadioButtonView

MotifLookPolicy>>radioButtonClass
	^MotifRadioButtonView

WindowBuilder 리소스 관리자

ObjectShare의 WindowBuilder 패키지는 단순한 비주얼 사용자 인터페이스 빌더 이상을 제공한다; 이 패키지에는 폰트나 비트맵과 같은 시스템 리소스를 위한 "관리자"를 몇 가지 포함한다. 각 관리자에는 사용자가 이러한 리소스를 관리하도록 여러 종류의 편집기 윈도우가 있어야 한다. 예를 들어, WBFontManager는 WBFontManagerWindow를 필요로 하고, WBBitmapManager는 WBBitmapManagerWindow를 필요로 하며, WBNLSManager는 WBNLSManagerWindow를 이용한다.

클라이언트가 만일 윈도우를 열어 사용자가 특정 리소스를 관리할 수 있도록 허용하려고 하는 경우, 클라이언트는 적절한 관리자에게 edit 메시지를 전송한다. 이 메서드는 모든 관리자 클래스의 상위클래스인 클래스 WBPoolManager에서 정의된다:

WBPoolManager>>edit
	self editorClass new openOn: self.

각 관리자 클래스는 editorClass 메서드에 그 클래스와 관련된 편집기 윈도우 클래스를 명시한다.

WBFontManager>>editorClass
	^WBFontManagerWindow

WBBitmapManager>>editorClass
	^WBBitmapManagerWindow

topPaneClass

비주얼 스몰토크에서 ViewManager 객체의 책임 중 한 가지는 애플리케이션의 윈도우와 창을 구성하는 것이다. 각 윈도우에는 최상위 창이 있다. 대부분 애플리케이션 윈도우의 경우, TopPane의 인스턴스가 최상위 창이 된다; 대화상자의 경우는 DialogTopPane이다.

ViewManager가 윈도우 생성 과정을 시작 시 그것은 아래 코드를 이용해 생성할 상위 창 클래스를 결정한다:

self topPaneClass new

WindowDialog는 topPaneClass를 상수 메서드로서 정의한다:

WindowDialog>>topPaneClass
	^DialogTopPane

그 외 ViewManager의 서브클래스에서는 다음 메서드가 인스턴스화시킬 클래스를 결정한다:

ViewManager>>topPaneClass
	Smalltalk includesKey: #MDISystem)
		ifFalse: [^TOPPane].
	Smalltalk isRunTime ifFalse: [
		(Smalltalk at: #MDISystem) isActive
			ifTrue: [^Smalltalk at: #MDIChild] ].
	^TopPane

다시 언급하지만, 가끔씩 팩토리 메서드는 변경이 불가능하게 하드코딩하는 대신 런타임 때 인스턴스화할 클래스를 결정하기도 한다.

외부 클래스를 위한 팩토리 메서드

최신 운영 환경에서는 때때로 스몰토크 내에 스몰토크가 아닌 (non-smalltalk) 클래스 혹은 외부 클래스의 이름을 명시할 필요가 있다. 예를 들어, 객체 요청 처리기(예: OLE 자동화)에 알려진 클래스 이름을 필요로 할 수도 있고, 자체 위젯의 "클래스" 이름을 함수인자로서 운영시스템 API 함수로 전달해야 하는 경우도 있다.

예를 들어, 비주얼 스몰토크가 버튼, 스크롤 바, 기타 위젯을 사용자 인터페이스에 생성시키기 위해 Windows95와 어떻게 상호작용하는지를 고려해보자. SubPane 서브클래스가 인스턴스화되어 열리면 비주얼 스몰토크는 Windows95에게 어떤 종류의 운연체제 위젯을 생성할 것인지 말해야 한다. 스몰토크 EntryField 클래스의 인스턴스를 열기 위해선 Windows95 클래스 이름이 "Edit"인 운영체제 위젯을 필요로 한다; 스몰토크 ToolBar 창은 Windows "ToolbarWindows32"에 상응한다. 이런 식으로 반복한다. 모든 서브클래스에 의해 상속된 SubPane>>buildWindows:는 다음 메시지를 포함한다:

self
	create: self windowClass
	title: self initialText
	style: ...

이 메시지로 호출된 메서드는 self windowClass가 명시하는 Windows 클래스 이르을 답하고, 이를 가지고 Windows95 함수를 호출하여 운영체제 위젯을 생성한다. SubPane의 구체적 서브클래스는 그에 상응하는 Windows95 위젯 클래스 이름을 리턴하기 위해 다음과 같이 고유의 windowClass 버전을 구현한다:

EntryField>>windowClass
	^'Edit'

이것은 스몰토크 밖의 클래스 이름에 사용한 팩토리 메서드 패턴의 예제이다.

관련 패턴

템플릿 메서드와 Constant 메서드

팩토리 메서드는 템플릿 메서드 (355) 패턴을 사용하고 때로는 Beck(1997)가 상수 메서드 패턴이라고 이름을 붙인 패턴을 사용하기도 한다는 사실은 이미 살펴보았다.

빌더와 추상 팩토리

팩토리 메서드는 빌더와 (47) 추상 팩토리의 (31) 구현에 사용할 수 있다. 앞에서 "예제코드"절에서 Builder 예제를 다루었다. 때로는 추상 팩토리 대신 팩토리 메서드를 사용할 수도 있다ㅡ두 패턴은 주요 애플리케이션으로부터 객체 생성 과정을 추상화하는 데 관한 동일한 문제에 대해 서로 경쟁되는 해법이기도 하다.

Notes

  1. 생각만큼 특이한 상황은 아니다. 예를 들어, 스몰토크/V에 사용되는 많은 애플리케이션들은 텍스트 위젯에 TextPane 클래스를 사용한다. 이 환경을 이용한 비주얼 스몰토크와 같은 최근 버전에서는 ParcPlace-Digitalk folks가 새로운 TextPaneControl 클래스를 구현하였는데, 이 클래스는 플랫폼의 자체 텍스트 위젯을 사용한다. 그 결과 TextPane보다는 TextPaneControl을 인스턴스화하기 위해 기존의 많은 애플리케이션이 변경되어야 했다. 그때마다 즉시 해결하지 않고 팩토리 메서드로 텍스트 창을 인스턴스화했다면 훨씬 더 간단한 작업이 되었을 것이다.
  2. 팩토리 메서드 패턴의 변종이 모델-뷰-컨트롤러 프레임워크에 어떻게 사용되는지 보였다. MVC를 일반적으로 더 알고 싶다면 다음 자료를 포함한 여러 서적과 기사에서 자세한 내용을 살펴볼 수 있다. Collins (1995), Hopkins and Horan (1995), Howard (1995), Krasner and Pope (1998), Lewis et al. (1995), Lewis (1995), Liu (1996).
  3. 여기서 이름 짓는 방식을 약간 변경하였음을 알 수 있다. 상수 메서드를 4CylinderEngine이라고 이름 지을 수 없었는데 그 이유는 숫자가 맨 앞에 올 수 없기 때문이다.