DesignPatternSmalltalkCompanion:FactoryMethod: Difference between revisions

From 흡혈양파의 번역工房
Jump to navigation Jump to search
(DPSC FACTORYMETHOD 페이지 추가)
 
(메소드 > 메서드 수정)
Line 12: Line 12:
===논의===
===논의===


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


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


일반적인 프레임워크에서는 한 계층구조의 객체들은ㅡ기반 클래스 Principal로 가정ㅡ두 번째 계층구조의 인스턴스와ㅡ최상위(topmost) 클래스 Associate로 가정ㅡ관련이 있다. 때때로 Principal 트리의 서로 다른 클래스는 Associate의 서로 다른 서브클래스와 협력해야 할 필요가 있다. 예를 들어, Principal의 서브클래스인 Principal2가 Associate2의 인스턴스와 쌍을 이루기도 하며, Principal3 객체가 Associate3 인스턴스와 협력해야 하는 경우도 있는 것이다. 팩토리 메소드는 Principal 계층구조의 각 클래스가 그와 협력하는 클래스를 명시하도록 하고, 올바른 클래스를 인스턴스화시키는 태스크만 가진 메소드에 이 결정을 위치시킴으로써 클래스 아키텍처를 촉진한다.
일반적인 프레임워크에서는 한 계층구조의 객체들은ㅡ기반 클래스 Principal로 가정ㅡ두 번째 계층구조의 인스턴스와ㅡ최상위(topmost) 클래스 Associate로 가정ㅡ관련이 있다. 때때로 Principal 트리의 서로 다른 클래스는 Associate의 서로 다른 서브클래스와 협력해야 할 필요가 있다. 예를 들어, Principal의 서브클래스인 Principal2가 Associate2의 인스턴스와 쌍을 이루기도 하며, Principal3 객체가 Associate3 인스턴스와 협력해야 하는 경우도 있는 것이다. 팩토리 메서드는 Principal 계층구조의 각 클래스가 그와 협력하는 클래스를 명시하도록 하고, 올바른 클래스를 인스턴스화시키는 태스크만 가진 메서드에 이 결정을 위치시킴으로써 클래스 아키텍처를 촉진한다.


후자의 설계 결정은 인스턴스화 태스크를 그 고유의 메소드로 깔끔하게 분리시킴으로써 전체 코드를 단순화시킨다. 이는 잠재적으로 발생하는 수 많은 코드의 중복을 피하게 해준다. 우리에게 Principal에 임의의 복잡한 메소드가 있는데 어느 시점이 되면 그에 상응하는 Associate 객체를 생성할 수도 있다; 팩토리 메소드를 이용해 우리는 순전히 Associate 객체 인스턴스화를 위해 구분된 메소드를 추상화한다. 팩토리 메소드가 없이는ㅡ즉시 인스턴스화할 클래스 이름을 하드코딩할 경우ㅡ다음과 같은 추상 클래스에 메소드를 가질 것이다.
후자의 설계 결정은 인스턴스화 태스크를 그 고유의 메서드로 깔끔하게 분리시킴으로써 전체 코드를 단순화시킨다. 이는 잠재적으로 발생하는 수 많은 코드의 중복을 피하게 해준다. 우리에게 Principal에 임의의 복잡한 메서드가 있는데 어느 시점이 되면 그에 상응하는 Associate 객체를 생성할 수도 있다; 팩토리 메서드를 이용해 우리는 순전히 Associate 객체 인스턴스화를 위해 구분된 메서드를 추상화한다. 팩토리 메서드가 없이는ㅡ즉시 인스턴스화할 클래스 이름을 하드코딩할 경우ㅡ다음과 같은 추상 클래스에 메서드를 가질 것이다.


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 31: Line 31:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 43: Line 43:
</syntaxhighlight>
</syntaxhighlight>


팩토리 메소드는 더 효과적이고 수월하며 확장가능한 해법을 제공한다. start 메소드의 부품, 즉 Principal 계층구조에서 모든 클래스에 동일하고 서브클래스마다 다른 조각(piece)을 abstract out하는 부품들을 변함없이 남겨둘 수 있다. 이 조각은 적절한 Associate 클래스를 인스턴스화시키는 코드에 불과하다; 이는 구분된 메소드, 팩토리 메소드로 이동된다. 이제 start는 다음과 같은 모습일 것이다:
팩토리 메서드는 더 효과적이고 수월하며 확장가능한 해법을 제공한다. start 메서드의 부품, 즉 Principal 계층구조에서 모든 클래스에 동일하고 서브클래스마다 다른 조각(piece)을 abstract out하는 부품들을 변함없이 남겨둘 수 있다. 이 조각은 적절한 Associate 클래스를 인스턴스화시키는 코드에 불과하다; 이는 구분된 메서드, 팩토리 메서드로 이동된다. 이제 start는 다음과 같은 모습일 것이다:


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 53: Line 53:
</syntaxhighlight>
</syntaxhighlight>


관련 객체의 생성에 대한 책임성은 주요 메소드로부터 의존적인 createAssociate 팩토리 메소드로 전이된다. 그 결과, 각 서브클래스는 start 메소드를 재정의할 필요 없이 createAssociate를 오버라이드할 수 있다. Principal과 그의 모든 서브클래스는 그들이 필요로 하는 어떤 Associate 클래스든 인스턴스화할 수 있도록 고유의 createAssociate 버전을 정의한다.
관련 객체의 생성에 대한 책임성은 주요 메서드로부터 의존적인 createAssociate 팩토리 메서드로 전이된다. 그 결과, 각 서브클래스는 start 메서드를 재정의할 필요 없이 createAssociate를 오버라이드할 수 있다. Principal과 그의 모든 서브클래스는 그들이 필요로 하는 어떤 Associate 클래스든 인스턴스화할 수 있도록 고유의 createAssociate 버전을 정의한다.


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 66: Line 66:
</syntaxhighlight>
</syntaxhighlight>


특정 Principal 클래스를 변경하기 위해 Associate 클래스를 변경할 필요가 있을 경우 그 한 부분만 변경이 가능하다는 또 다른 장점도 있다. 사실 Principal 클래스에는 Associate 서브클래스를 참조하는 다수의 메소드가 있다. 이 메소드들이 클래스 이름을 하드코딩하면 이름을 변경 시 모든 메소드마다 참조를 추적하는 과정이 수반된다. 이와 반대로, 팩토리 메소드를 사용 시 그 단일 메소드만 변경하면 되는 것이다. Associate를 참조하는 하나의 Principal 메소드만 있는 경우에도 이 참조를 찾기 위해 코드를 뚫을 필요가 없기 때문에 우리가 할 일은 훨씬 간단해진다; 수정하고 싶은 단일 팩토리 메소드를 찾는 것이 훨씬 수월하다.<ref name="주석5">생각만큼 특이한 상황은 아니다. 예를 들어, 스몰토크/V에 사용되는 많은 애플리케이션들은 텍스트 위젯에 TextPane 클래스를 사용한다. 이 환경을 이용한 비주얼 스몰토크와 같은 최근 버전에서는 ParcPlace-Digitalk folks가 새로운 TextPaneControl 클래스를 구현하였는데, 이 클래스는 플랫폼의 자체 텍스트 위젯을 사용한다. 그 결과 TextPane보다는 TextPaneControl을 인스턴스화하기 위해 기존의 많은 애플리케이션이 변경되어야 했다. 그때마다 즉시 해결하지 않고 팩토리 메소드로 텍스트 창을 인스턴스화했다면 훨씬 더 간단한 작업이 되었을 것이다.</ref>
특정 Principal 클래스를 변경하기 위해 Associate 클래스를 변경할 필요가 있을 경우 그 한 부분만 변경이 가능하다는 또 다른 장점도 있다. 사실 Principal 클래스에는 Associate 서브클래스를 참조하는 다수의 메서드가 있다. 이 메서드들이 클래스 이름을 하드코딩하면 이름을 변경 시 모든 메서드마다 참조를 추적하는 과정이 수반된다. 이와 반대로, 팩토리 메서드를 사용 시 그 단일 메서드만 변경하면 되는 것이다. Associate를 참조하는 하나의 Principal 메서드만 있는 경우에도 이 참조를 찾기 위해 코드를 뚫을 필요가 없기 때문에 우리가 할 일은 훨씬 간단해진다; 수정하고 싶은 단일 팩토리 메서드를 찾는 것이 훨씬 수월하다.<ref name="주석5">생각만큼 특이한 상황은 아니다. 예를 들어, 스몰토크/V에 사용되는 많은 애플리케이션들은 텍스트 위젯에 TextPane 클래스를 사용한다. 이 환경을 이용한 비주얼 스몰토크와 같은 최근 버전에서는 ParcPlace-Digitalk folks가 새로운 TextPaneControl 클래스를 구현하였는데, 이 클래스는 플랫폼의 자체 텍스트 위젯을 사용한다. 그 결과 TextPane보다는 TextPaneControl을 인스턴스화하기 위해 기존의 많은 애플리케이션이 변경되어야 했다. 그때마다 즉시 해결하지 않고 팩토리 메서드로 텍스트 창을 인스턴스화했다면 훨씬 더 간단한 작업이 되었을 것이다.</ref>


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


팩토리 메소드는 항상 템플릿 메소드 (355) 패턴을 이용해 구현된다. 템플릿 메소드를 다룬 단락에서는 패턴이 동일한 수신자에게서 수신되는 몇 개의 좁은(narrower) 메시지에 관련해 구현되는 넓은(broad) 메시지를 어떻게 수반하는지 설명한다. 더 넓은 템플릿 메소드는 상위클래스에서 정의되며, 그 클래스 또는 그의 서브클래스에서는 더 좁은 메소드가 구현된다. 템플릿 메소드는 스스로 메시지를 전송함으로써 기본 메소드(primitive method)를 호출한다. 팩토리 메소드 패턴의 컨텍스트에서는 팩토리 메소드가 기본 메소드이다. 여기서 설명하는 예제에서는 start가 템플릿 메소드, createAssociate가 기본 메소드가 된다.
팩토리 메서드는 항상 템플릿 메서드 (355) 패턴을 이용해 구현된다. 템플릿 메서드를 다룬 단락에서는 패턴이 동일한 수신자에게서 수신되는 몇 개의 좁은(narrower) 메시지에 관련해 구현되는 넓은(broad) 메시지를 어떻게 수반하는지 설명한다. 더 넓은 템플릿 메서드는 상위클래스에서 정의되며, 그 클래스 또는 그의 서브클래스에서는 더 좁은 메서드가 구현된다. 템플릿 메서드는 스스로 메시지를 전송함으로써 기본 메서드(primitive method)를 호출한다. 팩토리 메서드 패턴의 컨텍스트에서는 팩토리 메서드가 기본 메서드이다. 여기서 설명하는 예제에서는 start가 템플릿 메서드, createAssociate가 기본 메서드가 된다.


====문서처리 프레임워크====
====문서처리 프레임워크====


몇 가지 프레임워크에 적용된 팩토리 메소드를 살펴보자: [디자인 패턴]에서 설명된 예제와 함께 스몰토크 프레임워크의 선구자인 모델-뷰-컨트롤러(MVC)를 살펴보고자 한다.  
몇 가지 프레임워크에 적용된 팩토리 메서드를 살펴보자: [디자인 패턴]에서 설명된 예제와 함께 스몰토크 프레임워크의 선구자인 모델-뷰-컨트롤러(MVC)를 살펴보고자 한다.  
단순한 문서처리 애플리케이션을 위한 프레임워크는 [디자인 패턴] 편 DP 107페이지에 소개하고 있다. 그 프레임워크에서 도출한 예제를 제공할 것이다. 추상 클래스 Editor ([디자인 패턴]편에서는 Application이라 부름)와 Document는 키 추상화(key abstraction)뿐만 아니라 편집기와 문서 간 상호작용을 포함한다. 이러한 추상 클래스의 구체적 서브클래스를 인스턴스화시켜 그리기나 문서 편집기와 같은 편집기 종류를 생성할 수 있다.
단순한 문서처리 애플리케이션을 위한 프레임워크는 [디자인 패턴] 편 DP 107페이지에 소개하고 있다. 그 프레임워크에서 도출한 예제를 제공할 것이다. 추상 클래스 Editor ([디자인 패턴]편에서는 Application이라 부름)와 Document는 키 추상화(key abstraction)뿐만 아니라 편집기와 문서 간 상호작용을 포함한다. 이러한 추상 클래스의 구체적 서브클래스를 인스턴스화시켜 그리기나 문서 편집기와 같은 편집기 종류를 생성할 수 있다.
   
   
Editor 서브클래스와 Document 서브클래스 간에 독특하면서도 바람직한 매핑이 있다. 이 프레임워크의 그림 편집기는 문서의 작성에 접근성을 제공하고, 텍스트 편집기는 텍스트 파일의 편집을 가능하게 한다. 그림 편집기 애플리케이션의 경우, DrawingEditor가 Editor의 서브클래스로서 정의되고 DrawingDcoument는 Dcoument의 서브클래스로서 정의된다. 그리고 텍스트 편집기의 경우, TextEditor와 TextDcoument 서브클래스가 있다.  
Editor 서브클래스와 Document 서브클래스 간에 독특하면서도 바람직한 매핑이 있다. 이 프레임워크의 그림 편집기는 문서의 작성에 접근성을 제공하고, 텍스트 편집기는 텍스트 파일의 편집을 가능하게 한다. 그림 편집기 애플리케이션의 경우, DrawingEditor가 Editor의 서브클래스로서 정의되고 DrawingDcoument는 Dcoument의 서브클래스로서 정의된다. 그리고 텍스트 편집기의 경우, TextEditor와 TextDcoument 서브클래스가 있다.
프레임워크의 정의는 Editor에 단일 팩토리 메소드를 포함하는데 이는 다음과 같이 정의되어야 한다:
 
프레임워크의 정의는 Editor에 단일 팩토리 메서드를 포함하는데 이는 다음과 같이 정의되어야 한다:


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 88: Line 89:
</syntaxhighlight>
</syntaxhighlight>


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


[[image:dpsc_chapter03_FactoryMethod_02.png]]
[[image:dpsc_chapter03_FactoryMethod_02.png]]


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 99: Line 100:
</syntaxhighlight>
</syntaxhighlight>


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


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


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


{| style="border: 1px solid black;"
{| style="border: 1px solid black;"
Line 173: Line 174:


확실히 많은 클래스가 포함되어 있으며 새 클래스를 추가해야 할 수도 있다. 따라서 특정 View 클래스와 특정 Controller 클래스를 연관시킬 수 있는 간단하면서도 확장가능한 방법이 필요하다.  
확실히 많은 클래스가 포함되어 있으며 새 클래스를 추가해야 할 수도 있다. 따라서 특정 View 클래스와 특정 Controller 클래스를 연관시킬 수 있는 간단하면서도 확장가능한 방법이 필요하다.  
비주얼웍스의 설계자들은 이 문제에 접근하는 방법을 선택할 수 있다. 클래스 연관관계의 레지스트리를 도입할 수도 있다. 스몰토크에서는 View 클래스를 키로 가지고 관련 Controller 클래스를 값으로 가진 Dictionary에서 구현하는 것도 가능하다. 이 해법과 관련해 한 가지 문제점은 관리의 측면이다. 새로운 타입의 위젯에 대해 새로운 View 서브클래스가 정의되어야 한다고 가정하고, 이 View는 새로운 타입의 Controller를 필요로 한다고 치자. 필요한 클래스들을 정의하고 나면 레지스트리 업데이트를 기억해야 한다. 또한 모든 View 서브클래스는ㅡ심지어 상위클래스와 동일한 컨트롤러 타입을 사용하길 원하는 서브클래스도ㅡ레지스트리 엔트리를 필요로 한다.
비주얼웍스의 설계자들은 이 문제에 접근하는 방법을 선택할 수 있다. 클래스 연관관계의 레지스트리를 도입할 수도 있다. 스몰토크에서는 View 클래스를 키로 가지고 관련 Controller 클래스를 값으로 가진 Dictionary에서 구현하는 것도 가능하다. 이 해법과 관련해 한 가지 문제점은 관리의 측면이다. 새로운 타입의 위젯에 대해 새로운 View 서브클래스가 정의되어야 한다고 가정하고, 이 View는 새로운 타입의 Controller를 필요로 한다고 치자. 필요한 클래스들을 정의하고 나면 레지스트리 업데이트를 기억해야 한다. 또한 모든 View 서브클래스는ㅡ심지어 상위클래스와 동일한 컨트롤러 타입을 사용하길 원하는 서브클래스도ㅡ레지스트리 엔트리를 필요로 한다.
이를 대신해 비주얼웍스는 팩토리 메소드 패턴을 적용한다. View가 인스턴스화되고 나면 그 컨트롤러는 다음과 같이 생성된다. 클라이언트가 getController 메시지로 View의 컨트롤러를 처음으로 요청하면 지연 인스턴스화(lazy instantiation)로 그 생성을 처리한다 (이 코드는 비주얼웍스 이미지로부터 직접 얻은 것이다).  
 
이를 대신해 비주얼웍스는 팩토리 메서드 패턴을 적용한다. View가 인스턴스화되고 나면 그 컨트롤러는 다음과 같이 생성된다. 클라이언트가 getController 메시지로 View의 컨트롤러를 처음으로 요청하면 지연 인스턴스화(lazy instantiation)로 그 생성을 처리한다 (이 코드는 비주얼웍스 이미지로부터 직접 얻은 것이다).  


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 188: Line 191:
</syntaxhighlight>
</syntaxhighlight>


setController: 에서 뷰의 controller 인스턴스 변수는 self defaultController의 확장 결과로 설정된다. defaultController는 팩토리 메소드이다. 루트 뷰 클래스에서 이의 구현은 다음과 같다:
setController: 에서 뷰의 controller 인스턴스 변수는 self defaultController의 확장 결과로 설정된다. defaultController는 팩토리 메서드이다. 루트 뷰 클래스에서 이의 구현은 다음과 같다:


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 195: Line 198:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 205: Line 208:
</syntaxhighlight>
</syntaxhighlight>


그러나 패턴을 약간 변경한 경우 비주얼웍스는 추가로 간접지정 수준을 야기하기도 한다. 루트 뷰 클래스에 defaultController가 한 번 정의되었고, 이것은 defaultControllerClass라는 두 번째 메시지를 전송하여 검색된 클래스를 인스턴스화시킨다. defaultControllerClass 메소드는 인스턴스화되는 클래스를 단순히 되돌릴 뿐이다. 이제 서브클래스는 관련 Controller 클래스의 이름을 정하기 위해 defaultControllerClass 메소드만 오버라이드한다.  
그러나 패턴을 약간 변경한 경우 비주얼웍스는 추가로 간접지정 수준을 야기하기도 한다. 루트 뷰 클래스에 defaultController가 한 번 정의되었고, 이것은 defaultControllerClass라는 두 번째 메시지를 전송하여 검색된 클래스를 인스턴스화시킨다. defaultControllerClass 메서드는 인스턴스화되는 클래스를 단순히 되돌릴 뿐이다. 이제 서브클래스는 관련 Controller 클래스의 이름을 정하기 위해 defaultControllerClass 메서드만 오버라이드한다.  


defaultControllerClass의 기본 구현은 다음과 같다:
defaultControllerClass의 기본 구현은 다음과 같다:
Line 218: Line 221:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 229: Line 232:


상위클래스와 동일한 타입의 컨트롤러를 사용하길 원하는 클래스는 defaultControllerclass를 그대로 상속한다.  
상위클래스와 동일한 타입의 컨트롤러를 사용하길 원하는 클래스는 defaultControllerclass를 그대로 상속한다.  
이 해법에서 첫 번째 메소드는 “기본 인스턴스 얻기” 팩토리 메소드(defaultController)로서, 클래스 View에서 한 번 정의된 후 “기본 인스턴스의 클래스를 얻기”라는 임베디드(embedded) 기본 메소드를 (defaultControllerClass) 오버라이드할 수 있는 모든 서브클래스에 의해 상속되는 하나의 템플릿 메소드이기도 하다는 점을 명심한다. 이 메소드는 본질상 하나의 상수, 즉 하나의 클래스 객체를 돌려보낸다ㅡ자연스레 Beck (1997)는 Constant Method라는 이름을 붙인다. 팩토리 매소드 패턴의 이러한 변형은 템플릿 메소드와 Constant 메소드를 호출하여 실제 인스턴스의 인스턴스화로부터 클래스 명세를 분리한다.<ref name="주석6">팩토리 메소드 패턴의 변형이 모델-뷰-컨트롤러 프레임워크에 어떻게 사용되는지 보였다. MVC를 일반적으로 더 알고 싶다면 다음 자료를 포함한 여러 서적과 기사에서 자세한 내용을 살펴볼 수 있다. Collins (1995), Hopkins and Horan (1995), Howard (1995), Krasner and Pope (1998), Lewis et al. (1995), Lewis (1995), Liu (1996).</ref>
 
이 해법에서 첫 번째 메서드는 "기본 인스턴스 얻기" 팩토리 메서드(defaultController)로서, 클래스 View에서 한 번 정의된 후 "기본 인스턴스의 클래스를 얻기" 라는 임베디드(embedded) 기본 메서드를 (defaultControllerClass) 오버라이드할 수 있는 모든 서브클래스에 의해 상속되는 하나의 템플릿 메서드이기도 하다는 점을 명심한다. 이 메서드는 본질상 하나의 상수, 즉 하나의 클래스 객체를 돌려보낸다ㅡ자연스레 Beck (1997)는 Constant Method라는 이름을 붙인다. 팩토리 매소드 패턴의 이러한 변형은 템플릿 메서드와 Constant 메서드를 호출하여 실제 인스턴스의 인스턴스화로부터 클래스 명세를 분리한다.<ref name="주석6">팩토리 메서드 패턴의 변형이 모델-뷰-컨트롤러 프레임워크에 어떻게 사용되는지 보였다. MVC를 일반적으로 더 알고 싶다면 다음 자료를 포함한 여러 서적과 기사에서 자세한 내용을 살펴볼 수 있다. Collins (1995), Hopkins and Horan (1995), Howard (1995), Krasner and Pope (1998), Lewis et al. (1995), Lewis (1995), Liu (1996).</ref>


===구현===
===구현===
Line 235: Line 239:
====3가지 기본방식====
====3가지 기본방식====


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 248: Line 252:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 269: Line 273:
</syntaxhighlight>
</syntaxhighlight>


이것은 런타임 객체가 되는 스몰토크의 클래스에 따라 좌우되는 해법 중 하나이다. 클래스는 메시지에 응답하는 능력이 있기 때문에 defaultCollaboratorClass는 쉽게 클래스 객체를 반환하고, new 메시지를 전송하기 위해 이를 defaultCollaborator에 남겨둔다. C++의 경우에는 클래스 객체가 없기 때문에 이런 방식으로 패턴을 구현할 수 없을 것이다; C++에서 팩토리 메소드가 클래스를 인스턴스화하기 위해선 단순히 “명명하기(naming)”보다는 새 인스턴스를 반환해야만 한다.
이것은 런타임 객체가 되는 스몰토크의 클래스에 따라 좌우되는 해법 중 하나이다. 클래스는 메시지에 응답하는 능력이 있기 때문에 defaultCollaboratorClass는 쉽게 클래스 객체를 반환하고, new 메시지를 전송하기 위해 이를 defaultCollaborator에 남겨둔다. C++의 경우에는 클래스 객체가 없기 때문에 이런 방식으로 패턴을 구현할 수 없을 것이다; C++에서 팩토리 메서드가 클래스를 인스턴스화하기 위해선 단순히 "이름짓기(naming)"보다는 새 인스턴스를 반환해야만 한다.


3. 3번째는 약간만 변형을 준 해법으로, 인스턴스화가 메인 메소드에 발생하고, Constant 보조 메소드(auxiliary method)가 인스턴스화할 클래스를 반환한다 (변형 2와 마찬가지로):
3. 3번째는 약간만 변형을 준 해법으로, 인스턴스화가 메인 메서드에 발생하고, Constant 보조 메서드(auxiliary method)가 인스턴스화할 클래스를 반환한다 (변형 2와 마찬가지로):


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 284: Line 288:
</syntaxhighlight>
</syntaxhighlight>


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


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


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


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 296: Line 300:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 303: Line 307:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 322: Line 326:
</syntaxhighlight>
</syntaxhighlight>


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


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


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


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 343: Line 347:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 355: Line 359:
</syntaxhighlight>
</syntaxhighlight>


구현을 완료하기 위해선 빌더 특정적 팩토리 메소드를 구현할 필요가 있다:<ref name="주석7">여기서 ng convention을 약간 변경하였음을 참고한다. Constant Method 4CylinderEngine이라고 name할 수 없었는데 그 이유는 숫자가 맨 앞에 올 수 없기 때문이다.</ref>
구현을 완료하기 위해선 빌더 특정적 팩토리 메서드를 구현할 필요가 있다:<ref name="주석7">여기서 ng convention을 약간 변경하였음을 참고한다. Constant Method 4CylinderEngine이라고 name할 수 없었는데 그 이유는 숫자가 맨 앞에 올 수 없기 때문이다.</ref>


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 366: Line 370:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 373: Line 377:
</syntaxhighlight>
</syntaxhighlight>


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


===알려진 스몰토크 사용예===
===알려진 스몰토크 사용예===


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


====비주얼 스몰토크 윈도우 정책====
====비주얼 스몰토크 윈도우 정책====
Line 387: Line 391:
</syntaxhighlight>
</syntaxhighlight>


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


일부 ViewManager 서브클래스는 최근 컨텍스트에 따라 어떤 정책 클래스를 사용할지 런타임 시 결정하는 메소드를 이용해 windowPolicyClass를 오버라이드한다. 예를 들어, 최근 실행 중인 이미지가 스몰토크 개발 환경일 경우 Browser>>windowPolicyClass는 하나의 클래스를 반환하고, 그렇지 않을 경우 다른 클래스를 반환한다.
일부 ViewManager 서브클래스는 최근 컨텍스트에 따라 어떤 정책 클래스를 사용할지 런타임 시 결정하는 메서드를 이용해 windowPolicyClass를 오버라이드한다. 예를 들어, 최근 실행 중인 이미지가 스몰토크 개발 환경일 경우 Browser>>windowPolicyClass는 하나의 클래스를 반환하고, 그렇지 않을 경우 다른 클래스를 반환한다.


====비주얼웍스 모양 정책====
====비주얼웍스 모양 정책====


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


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 404: Line 408:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 419: Line 423:
====WindowBuilder 리소스 관리자====
====WindowBuilder 리소스 관리자====


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


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 428: Line 432:
</syntaxhighlight>
</syntaxhighlight>


각 관리자 클래스는 editorClass 메소드에 클래스와 관련된 편집기 윈도우 클래스를 명시하는데, 아래에 예를 제시하고자 한다.
각 관리자 클래스는 editorClass 메서드에 클래스와 관련된 편집기 윈도우 클래스를 명시하는데, 아래에 예를 제시하고자 한다.


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 448: Line 452:
</syntaxhighlight>
</syntaxhighlight>


WindowDialog는 topPaneClass를 Constant 메소드로서 정의한다:
WindowDialog는 topPaneClass를 Constant 메서드로서 정의한다:


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 455: Line 459:
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 467: Line 471:
</syntaxhighlight>
</syntaxhighlight>


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


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


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


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


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 482: Line 486:
</syntaxhighlight>
</syntaxhighlight>


이 메시지로 호출된 메소드는 Windows95 함수를 호출하는데 이 함수는 selfwindowClass가 Windows 클래스를 명시하는 자체 화면 위젯을 생성한다. SubPane의 구체적 서브클래스는 그에 상응하는 Windows95 위젯 클래스 이름을 리턴하기 위해 다음과 같이 고유의 windowClass 버전을 구현한다:
이 메시지로 호출된 메서드는 Windows95 함수를 호출하는데 이 함수는 selfwindowClass가 Windows 클래스를 명시하는 자체 화면 위젯을 생성한다. SubPane의 구체적 서브클래스는 그에 상응하는 Windows95 위젯 클래스 이름을 리턴하기 위해 다음과 같이 고유의 windowClass 버전을 구현한다:


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 489: Line 493:
</syntaxhighlight>
</syntaxhighlight>


이것은 팩토리 메소드 패턴의 예제이지만 스몰토크 외부의 클래스 이름에 사용할 경우를 위한 것이다.
이것은 팩토리 메서드 패턴의 예제이지만 스몰토크 외부의 클래스 이름에 사용할 경우를 위한 것이다.


===관련 패턴===
===관련 패턴===


====템플릿 메소드와 Constant 메소드====
====템플릿 메서드와 Constant 메서드====


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


====빌더와 추상 팩토리====
====빌더와 추상 팩토리====


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


==Notes==
==Notes==
<references />
<references />
[[Category:DesignPatternSmalltalkCompanion]]
[[Category:DesignPatternSmalltalkCompanion]]

Revision as of 10:36, 8 January 2013

FACTORY METHOD (DP 107)

의도

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

구조

Dpsc chapter03 FactoryMethod 01.png

논의

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

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

일반적인 프레임워크에서는 한 계층구조의 객체들은ㅡ기반 클래스 Principal로 가정ㅡ두 번째 계층구조의 인스턴스와ㅡ최상위(topmost) 클래스 Associate로 가정ㅡ관련이 있다. 때때로 Principal 트리의 서로 다른 클래스는 Associate의 서로 다른 서브클래스와 협력해야 할 필요가 있다. 예를 들어, Principal의 서브클래스인 Principal2가 Associate2의 인스턴스와 쌍을 이루기도 하며, Principal3 객체가 Associate3 인스턴스와 협력해야 하는 경우도 있는 것이다. 팩토리 메서드는 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 계층구조에서 모든 클래스에 동일하고 서브클래스마다 다른 조각(piece)을 abstract out하는 부품들을 변함없이 남겨둘 수 있다. 이 조각은 적절한 Associate 클래스를 인스턴스화시키는 코드에 불과하다; 이는 구분된 메서드, 팩토리 메서드로 이동된다. 이제 start는 다음과 같은 모습일 것이다:

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

관련 객체의 생성에 대한 책임성은 주요 메서드로부터 의존적인 createAssociate 팩토리 메서드로 전이된다. 그 결과, 각 서브클래스는 start 메서드를 재정의할 필요 없이 createAssociate를 오버라이드할 수 있다. Principal과 그의 모든 서브클래스는 그들이 필요로 하는 어떤 Associate 클래스든 인스턴스화할 수 있도록 고유의 createAssociate 버전을 정의한다.

Principal>>createAssociate
	self subclassResponsibility

Principal2>>createAssociate
	^Associate2 new

Principal3>>createAssociate
	^Associate3 new

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

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

팩토리 메서드는 항상 템플릿 메서드 (355) 패턴을 이용해 구현된다. 템플릿 메서드를 다룬 단락에서는 패턴이 동일한 수신자에게서 수신되는 몇 개의 좁은(narrower) 메시지에 관련해 구현되는 넓은(broad) 메시지를 어떻게 수반하는지 설명한다. 더 넓은 템플릿 메서드는 상위클래스에서 정의되며, 그 클래스 또는 그의 서브클래스에서는 더 좁은 메서드가 구현된다. 템플릿 메서드는 스스로 메시지를 전송함으로써 기본 메서드(primitive method)를 호출한다. 팩토리 메서드 패턴의 컨텍스트에서는 팩토리 메서드가 기본 메서드이다. 여기서 설명하는 예제에서는 start가 템플릿 메서드, createAssociate가 기본 메서드가 된다.

문서처리 프레임워크

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

Editor 서브클래스와 Document 서브클래스 간에 독특하면서도 바람직한 매핑이 있다. 이 프레임워크의 그림 편집기는 문서의 작성에 접근성을 제공하고, 텍스트 편집기는 텍스트 파일의 편집을 가능하게 한다. 그림 편집기 애플리케이션의 경우, DrawingEditor가 Editor의 서브클래스로서 정의되고 DrawingDcoument는 Dcoument의 서브클래스로서 정의된다. 그리고 텍스트 편집기의 경우, TextEditor와 TextDcoument 서브클래스가 있다.

프레임워크의 정의는 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>>createDcoument는 호출되면서 끝이 나고, 새로운 TextDocument를 생성한다. 이와 비슷하게, DrawingEditor의 createDcoument 메서드가 실행 시 새로운 DrawingDocument를 생성한다. 따라서 팩토리 메서드는 일반화된 메커니즘에 의해 바람직한 애플리케이션 특정적 관계를 시행한다ㅡTextEditor->TextDcoument과 DrawingEditor->DrawingDcoument.

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 클래스를 연관시킬 수 있는 간단하면서도 확장가능한 방법이 필요하다.

비주얼웍스의 설계자들은 이 문제에 접근하는 방법을 선택할 수 있다. 클래스 연관관계의 레지스트리를 도입할 수도 있다. 스몰토크에서는 View 클래스를 키로 가지고 관련 Controller 클래스를 값으로 가진 Dictionary에서 구현하는 것도 가능하다. 이 해법과 관련해 한 가지 문제점은 관리의 측면이다. 새로운 타입의 위젯에 대해 새로운 View 서브클래스가 정의되어야 한다고 가정하고, 이 View는 새로운 타입의 Controller를 필요로 한다고 치자. 필요한 클래스들을 정의하고 나면 레지스트리 업데이트를 기억해야 한다. 또한 모든 View 서브클래스는ㅡ심지어 상위클래스와 동일한 컨트롤러 타입을 사용하길 원하는 서브클래스도ㅡ레지스트리 엔트리를 필요로 한다.

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

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에서 한 번 정의된 후 "기본 인스턴스의 클래스를 얻기" 라는 임베디드(embedded) 기본 메서드를 (defaultControllerClass) 오버라이드할 수 있는 모든 서브클래스에 의해 상속되는 하나의 템플릿 메서드이기도 하다는 점을 명심한다. 이 메서드는 본질상 하나의 상수, 즉 하나의 클래스 객체를 돌려보낸다ㅡ자연스레 Beck (1997)는 Constant Method라는 이름을 붙인다. 팩토리 매소드 패턴의 이러한 변형은 템플릿 메서드와 Constant 메서드를 호출하여 실제 인스턴스의 인스턴스화로부터 클래스 명세를 분리한다.[2]

구현

3가지 기본방식

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

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

Application>>defaultCollaborator
	^Collaborator new

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

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는 쉽게 클래스 객체를 반환하고, new 메시지를 전송하기 위해 이를 defaultCollaborator에 남겨둔다. C++의 경우에는 클래스 객체가 없기 때문에 이런 방식으로 패턴을 구현할 수 없을 것이다; C++에서 팩토리 메서드가 클래스를 인스턴스화하기 위해선 단순히 "이름짓기(naming)"보다는 새 인스턴스를 반환해야만 한다.

3. 3번째는 약간만 변형을 준 해법으로, 인스턴스화가 메인 메서드에 발생하고, Constant 보조 메서드(auxiliary method)가 인스턴스화할 클래스를 반환한다 (변형 2와 마찬가지로):

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

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

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

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

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

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

Editor class instance VariableNames: '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

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

CarBuilder>>add4CylinderEngine
	self car addEngine: self fourCylinderEngine

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

알려진 스몰토크 사용예

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

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

비주얼 스몰토크에서 각 TopPane은 (애프리케이션 윈도우를 나타냄) WindowPolicy의 서브클래스들 중 한 서브클래스의 인스턴스와 관련이 있다. WindowPolicy 객체들은 애플리케이션 윈도우의 메뉴 바를 구성하도록 돕는다. 서브클래스는 각자 다른 풀다운 메뉴를 메뉴 바에 추가한다. StandardWindowPolicy 객체는 File과 Edit 메뉴를 추가하고, 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 패키지에서는 비주얼 사용자 인터페이스 Builder 이상을 얻는다; 이 패키지에는 폰트나 비트맵과 같은 시스템 리소스를 위한 "관리자"를 몇 가지 포함하기도 한다. 각 관리자는 사용자가 이러한 리소스를 관리하도록 여러 타입의 편집기 윈도우를 필요로 한다. 예를 들어, 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를 Constant 메서드로서 정의한다:

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 자동화)에 알려진 클래스 이름을 필요로 할 수도 있고, 자체 위젯의 "클래스" 이름을 argument로서 운영시스템 API 함수로 전달해야 하는 경우도 있다.

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

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

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

EntryField>>windowClass
	^'Edit'

이것은 팩토리 메서드 패턴의 예제이지만 스몰토크 외부의 클래스 이름에 사용할 경우를 위한 것이다.

관련 패턴

템플릿 메서드와 Constant 메서드

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

빌더와 추상 팩토리

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

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. 여기서 ng convention을 약간 변경하였음을 참고한다. Constant Method 4CylinderEngine이라고 name할 수 없었는데 그 이유는 숫자가 맨 앞에 올 수 없기 때문이다.