이번 포스팅에서는 팩토리 메서드 패턴에 대해서 알아보도록 하려고 한다.
팩토리 메서드 패턴은 다른 말로 가상 생성자(Virtual Constructor) 패턴 이라고도 한다. 팩토리 메서드 패턴의 의도는 객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 하는 것이다.
기존의 문제점
프레임워크는 추상클래스를 사용하여 객체 간의 관련성을 정의하고 유지할 수 있다. 또한 프레임워크는 이들 객체를 생성할 책임을 지니기도 한다. 따라서 추상클래스를 상속하는 서브클래스에서 구체적인 행동을 어떻게 정의할 것인지는 프레임워크에서 중요하게 생각해야 하는 요소이다.
예를 들어 사용자에게 여러가지 문서를 보여주는 프레임워크를 만든다고 생각해 보자. 먼저 Application 클래스와 Document 클래스라는 두 개의 큰 추상화를 가지고 시작한다. 두 클래스는 추상클래스이기 때문에 서브클래스를 만들어서 종속적인 구현을 진행해야 한다. 여기서 문제는 우리가 어떤 프로그램을 만드는지에 따라 Document의 서브클래스가 달라지기 때문에, Application 클래스는 어떤 문서의 인스턴스를 생성해야 하는지를 알 수가 없다. 단지 언제 문서의 인스턴스를 만들어야 하는지만 알고 있을 뿐이다.
팩토리 메서드 패턴은 이러한 상황에서 해결 방법을 제시한다. Document의 서브클래스 중 어떤 것을 생성해야 하는지에 대한 정보를 캡슐화하고, 그것을 프레임워크에서 떼어낸다. 위의 예제에서는 Application 클래스의 서브클래스는 추상화된 CreateDocument() 연산을 재정의하여 적당한 Document 클래스의 서브클래스를 반환하도록 한다. 여기서 CreateDocument() 연산을 팩토리 메서드라고 하는데, 객체를 제조하는(manufacture) 방법을 알기 때문이다.
팩토리 메서드의 활용성
팩토리 메서드는 다음 상황에서 사용할 수 있다.
- 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없을 때, 즉 객체 생성의 변화가 자주 일어나는 경우
- 생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정했으면 할 때
- 객체 생성의 책임을 몇 개의 보조 서브클래스 가운데 하나에게 위임하고, 어떤 서브클래스가 위임자인지에 대한 정보를 국소화 시키고 싶을 때
구조 및 참여자
팩토리 메서드 패턴에서 전체 클래스 및 메서드 구조는 다음과 같이 가져간다.
이 패턴의 참여자들은 다음과 같다.
- Product (예제에서 Document) : 팩토리 메서드가 생성하는 객체의 인터페이스를 정의한다.
- ConcreteProduct (MyDocument) : product 클래스에 정의된 인터페이스를 실제로 구현한다.
- Creator (Application) : Product 타입의 객체를 반환하는 팩토리 메서드를 선언한다. Creator 클래스는 팩토리 메서드를 기본적으로 구현하는데, 이 구현에서는 ConcreteProduct 객체를 반환한다. 또한 Product 객체 생성을 위해 팩터리 메서드를 호출한다.
- ConcreteCreator (MyApplication) : 팩토리 메서드를 재정의하여 ConcreteProduct의 인스턴스를 반환한다.
협력하는 방법은 Creator가 자신의 서브클래스를 통해 실제 필요한 팩토리 메서드를 정의하여 적절한 ConcreteProduct의 인스턴스를 반환할 수 있게 하는 식으로 진행한다.
결과
팩토리 메서드 패턴을 사용하면 얻을 수 있는 결과는 다음과 같다.
- 응용프로그램에 국한된 클래스가 코드에 종속되지 않게 해준다.
- 응용프로그램은 Product 클래스에 정의된 인터페이스와만 동작하도록 코드가 만들어지기 때문에, 사용자가 정의한 어떤 ConcreteProduct 클래스와도 동작할 수 있게 된다.
- 서브클래스에 대한 훅 메서드를 제공한다
- 팩토리 메서드로 클래스 내부에서 객체를 생성하는 것이 객체를 직접 생성하는 것보다 훨씬 응용성이 높아진다.
- 팩토리 메서드 패턴에서는 객체별로 서로 다른 버전을 제공하는 훅 기능을 서브클래스에 정의한다.
- 병렬적인 클래스 계통을 연결하는 역할을 담당한다.
- 병렬적 클래스 계통은 클래스가 자신의 책임을 분리된 다른 클래스에 위임할 때 발생한다.
- 예를 들어 그래픽 객체가 마우스로 크기를 조정하는 기능은 그림 자체에는 저장될 필요가 없다. 그림마다 다른 방식으로 구현해 주어야 하기 때문이다. 이런 경우 별도 Manipultor 객체를 사용하는 것이 바람직하다.
구현
팩토리 메서드 패턴을 구현할 때는 다음 사항들을 고려하여야 한다. 예제 코드는 C++로 작성했다.
1. 구현 방법이 두 가지이다.
첫 번째는 Creator 클래스를 추상 클래스로 정의하고, 정의한 팩토리 메서드에 대한 구현은 제공하지 않는 경우이다. 두 번째는 Creator가 구체 클래스이고, 팩토리 메서드에 대한 기본 구현을 제공하는 경우이다.
추상클래스로 정의할 때는 구현을 제공한 서브클래스를 반드시 정의해야 한다. 예측 할 수 없는 클래스를 생성해야 하는 문제가 생기지만, 구체 클래스로 정의할 때는 Creator가 팩토리 메서드를 사용하여 유연성을 보장할 수 있다. 예를 들면 "객체의 생성은 별도의 연산으로 분리하여, 이 연산을 서브클래스에서 재정의하게 합니다."라는 규칙을 정의하여, 서브클래스 설계자는 부모 클래스가 인스턴스를 만드는 객체의 클래스를 변경할 수 있다.
2. 팩토리 메서드를 매개변수화 한다.
팩토리 메서드가 매개 변수를 받아서 어떤 종류의 제품을 생성할지 식별하게 만들 수 있다. 매개변수화된 팩토리 메서드는 다음과 같은 일반적인 형태를 가진다. 아래의 예제 코드에서는 MyProduct와 YourProduct가 Product 클래스의 서브클래스들로 되어 있다.
class Creator {
public:
virtual Product* Create(ProductId);
};
Product* Creator::Create (ProductId id) {
if (id == MINE) return MyProduct;
if (id == YOURS) return yourProduct;
// ...
return 0;
}
매개변수화된 팩토리 메서드를 오버라이드하면, Creator 클래스가 생성하는 제품을 쉽게 확장하거나 변경할 수 있다. 새로운 제품에 대한 식별자를 추가하거나, 기존의 식별자를 다른 제품과 연결할 수 있다. 아래의 예제는 MyCreator라는 서브클래스가 MyProduct와 YourProduct를 교체하고 새로운 TheirProduct라는 서브클래스를 지원하도록 한다.
Product* MyCreator::Create (ProductId id) {
if (id == YOURS) return new MyProduct;
if (id == MINE) return new yourProduct;
// YOURS와 MINE을 바꿀 때
if (id == THEIRS) return new TheirProduct;
return Creator::Create(id); // 다른 모든 것들이 실패하면 호출
}
3. 템플릿을 사용하여 서브클래싱을 피한다.
팩토리 메서드를 쓰면 생길 수 있는 잠재적인 문제점 중 하나는 Product 클래스 하나를 추가하려 할 때마다 서브클래싱을 해야 한다는 점이다. 이로 인하여 클래스 계통의 부피가 확장되는 문제가 생길 수 있다.
C++에서 이러한 문제를 해결할 수 있는 방법 중 하나는 Creator 클래스의 서브클래스가 되는 템플릿 클래스를 정의하고 이것이 Product 클래스로 매개변수화되도록 만드는 것이다. 아래의 예제를 확인해보자.
class Creator {
public:
virtual Product* CreateProduct() = 0;
};
template <class TheProduct>
class StandardCreator: public Creator {
// Creator를 상속받는 팩토리 메서드를 구현할 서브클래스를 템플릿 클래스로 정의
public:
virtual Product* CreaeteProduct();
};
template <class TheProduct>
Product* StandardCreator<TheProduct>::CreateProduct () {
return new TheProduct;
}
이 템플릿 클래스를 이용하면, 사용자는 Creator를 상속받는 서브클래스를 정의할 필요 없이, 적절한 Product 클래스만 준비해 놓으면 된다.
class MyProduct : public Product {
public:
MyProduct();
// ...
};
StandardCreator<MyProduct> myCreator;
참고자료
- <GoF의 디자인패턴> 에릭 감마 외 3인 저
- gmlwjd9405.github.io/2018/08/07/factory-method-pattern.html
'Computer Sci. > Design Pattern' 카테고리의 다른 글
Law of Demeter (디미터 법칙) (0) | 2022.11.01 |
---|---|
MVC, MVP 그리고 MVVM 패턴에 대하여 (0) | 2022.06.07 |
[디자인 패턴] 추상 팩토리 패턴 (Abstract Factory Pattern) (0) | 2020.09.23 |
<GoF의 디자인패턴> 1. 서론 (0) | 2020.07.27 |