본문 바로가기
SW

GoF의 디자인 패턴(Design Patterns: Elements of Reusable Object-Oriented Software) - 3장 생성 패턴 :: 팩토리 메서드(Factory Method)

by 라꾸스떼(YR) 2020. 4. 20.
반응형

[팩토리 메서드(Factory Method) - 클래스 생성(Class Creational)]

<의도>

객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 한다.

 

<다른 이름>

가상 생성자(Virtual Constructor)

 

<동기>

프레임워크는 추상 클래스를 사용하여 객체 간의 관련성을 정의하고 유지할 수 있다. 또한 프레임워크는 이들 객체를 생성할 책임을 지닌다. 프레임워크는 이들 추상 클래스 간의 상호작용을 책임져서 전체 시스템의 기본 동작 방식을 정의한다. 그리고 이들 추상 클래스를 상속하는 서브클래스에서 구체적인 행동을 정의하믕로써 새로운 응용프로그램을 만든다.

프레임워크는 클래스를 인스턴스로 만들어야 하지만, 추상 클래스 밖에 모르는 프레임워크는 클래스의 인스턴스화 작업을 수행할 수없다. 왜냐하면 추상 클래스는 인스턴스를 가질 수 없기 때문이다.

팩토리 메서드 패턴은 이런 문제에 대한 해법을 제시한다. 이 패턴은 Document의 서브클래스 중 어느 것을 생성해야 하는지에 대한 정보를 캡슐화하고, 그것을 프레임워크에서 떼어낸다.

Application 클래스의 서브클래스는 추상화된 CreateDocument() 연산을 재정의하여 적당한 Document 클래스의 서브클래스를 반환하도록 한다. CreateDocument() 연산을 가리켜 팩토리 메서드라고 하는데, 객체를 "제조하는(manufacture)" 방법을 알기 때문이다.

 

<활용성>

-어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없을 때

-생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정했으면 할 때

-객체 생성의 책임을 몇 개의 보조 서브클래스 가운데 하나에게 위임하고, 어떤 서브클래스가 위임자인지에 대한 정보를 국소화시키고 싶을 때

 

<참여자>

-Product : 팩토리 메서드가 생성하는 객체의 인터페이스를 정의한다.

-ConcreteProduct : Product 클래스에 정의된 인터페이스를 실제로 구현한다.

-Creator : Product 타입의 객체를 반환하는 팩토리 메서드를 선언한다. Creator 클래스는 팩토리 메서드를 기본적으로 구현하는데, 이 구현에서는 ConcreteProduct 객체를 반환한다. 또한 Product 객체의 생성을 위해 팩토리 메서드를 호출한다.

-ConcreteCreator : 팩토리 메서드를 재정의하여 ConcreteProduct의 인스턴스를 반환한다.

 

<협력 방법>

Creator는 자신의 서브클래스를 통해 실제 필요한 팩토리 메서드를 정의하여 적절한 ConcreteProduct의 인스턴스를 반환할 수 있게 한다.

 

<결과>

팩토리 메서드 패턴은 응용프로그램에 국한된 클래스가 코드에 종속되지 않도록 해준다. 응용프로그램은 Product 클래스에 정의된 인터페이스와만 동작하도록 코드가 만들어지기 때문에, 사용자가 정의한 어떤 ConcreteProduct 클래스와도 동작할 수 있게 된다. 팩토리 메서드의 잠재적인 단점은 사용자가 ConcreteProduct 객체 하나만 만들려할 때에도 Creator 클래스를 서브클래싱해야 할지 모른다는 점이다.

1.서브클래스에 대한 훅(hook) 메서드를 제공한다. 팩토리 메서드로 클래스 내부에서 객체를 생성하는 것이 객체를 직접 생성하는 것보다 훨씬 응용성이 높아진다. 팩토리 메서드 패턴에서는 객체별로 서로 다른 버전을 제공하는 훅기능을 서브클래스에 정의한다.

2.병렬적인 클래스 계통을 연결하는 역할을 담당한다. 병렬적 클래스 계통은 클래스가 자신의 책임을 분리된 다른 클래스에 위임할 때 발생한다.

 

<구현>

1.구현 방법이 크게 2가지이다. (1)Creator 클래스를 추상 클래스로 정의하고, 정의한 팩토리 메서드에 대한 구현은 제공하지 않는 경우와 (2)Creator가 구체 클래스이고, 팩토리 메서드에 대한 기본 구현을 제공하는 경우이다. 추상 클래스로 정의할 때는 구현을 제공한 서브클래스를 반드시 정의해야 한다. 아직 예측할 수 없는 클래스들을 생성해야 하는 문제가 생긴다. 구체 클래스로 정의할 때는 Creator가 팩토리 메서드를 사용하여 유연성을 보장할 수 있다. "객체의 생성은 별도의 연산으로 분리하여, 이 연산을 서브클래스에서 재정의하게 한다" 이 규칙을 따르면, 서브클래스 설계자는 부모 클래스가 인스턴스를 만드는 객체의 클래스를 변경할 수 있다.

2.팩토리 메서드를 매개변수화한다. 팩토리 메서드를 이용해서 여러 종류의 제품을 생성하는 방법도 있다. 팩토리 메서드가 매개변수를 받아서 어떤 종류의 제품을 생성할지 식별하게 만드는 것이다. 팩토리 메서드가 생성하는 모든 객체는 Product라는 인터페이스를 만족해야 한다. CreateDocument() 메서드에 생성할 문서 객체의 종류를 매개변수로 넘겨주면 된다. 매개변수화된 팩토리 메서드를 오버라이드하면, Creator 클래스가 생성하는 제품을 쉽게 확장하거나 변경할 수 있다. 새로운 종류의 제품에 대한 식별자를 추가하거나, 기존의 식별자를 다른 제품과 연결할 수 있다.

3.언어마다 구현 방법이 조금 다를 수 있다. C++에서라면 팩토리 메서드는 가상 함수로 정의되어야 할 뿐만 아니라 순수 가상 함수로 정의될 때도 꽤 많다. 그렇기 때문에 Creator 클래스의 생성자에 이 팩토리 메서드를 호출하지 않도록 주의해야 한다. 다시 말해, 부모 클래스에 정의된 팩토리 함수가 가상 함수이고 게다가 순수 가상 함수이기 때문에, 구현은 전혀 정의되어 있지 않다. 필요하면 제품을 생성하는 접근자(accessor) 연산을 통해서만 제품에 접근함으로써 이런 문제를 해결할 수 있다. 그러므로 생성자에게 제품을 생성한다고 생각하지 않고 초기 생성자에서는 그냥 어떤 의미도 없는 0과 같은 값으로 제품에 대한 매개변수를 초기화하고, 접근자가 제품을 반환하도록 한다. 물론 이전에 제품이 생성되어 있는지 먼저 확인하고, 만약 제품이 존재하지 않는다면 접근자 연산은 이를 먼저 생성해야 한다. 생성자가 초기화시키는 것이 아니라 필요한 시점에서 초기화를 수행하기 때문에 이런 기법을 가리켜 지연 초기화(lazy initialization)이라고 한다.

4.템플릿을 사용하여 서브클래싱을 피한다. 잠재적인 문제점 중 하나는 그냥 Product 클래스 하나를 추가하려 할 때마다 서브클래싱을 해야 한다는 점이다. C++에서 이런 문제를 해결할 수 있는 방법 중 하나는 Creator 클래스의 서브클래스가 되는 템플릿 클래스를 정의하고 이것이 Product 클래스로 매개변수화되도록 만드는 것이다.

5.명명 규칙을 따르는 것도 매우 중요한 일이다.

 

<예제 코드>

CreateMaze() 함수는 미로를 만들어 반환한다. 이 함수의 문제는 미로, 방, 문, 벽 클래스를 직접 코딩한다는 것이다. 이 문제를 해결하는 한 방법으로, 팩토리 메서드를 이용하여 서브클래스들이 이 요소들을 선택할 수 있도록 한다.

 

<잘 알려진 사용예>

팩토리 메서드는 툴킷과 프레임워크 구현에서 많이 볼 수 있다.

 

<관련 패턴>

추상 팩토리 패턴은 이 팩토리 메서드를 이용해서 구현할 때가 많다. 팩토리 메서드는 템플릿 메서드 패턴에서도 사용될 때가 많다. 원형 패턴은 Creator 클래스의 상속이 필요하지는 않다. 그러나 Product 클래스에 정의된 초기화 연산은 필요하다. Creator 클래스는 객체의 초기화를 위해 초기화 연산을 사용하지만, 팩토리 메서드는 이런 연산이 필요하지 않다.

반응형

댓글