팩토리 메서드 (Factory Method)
1. 의도
- 객체를 생성하기 위한 인터페이스를 정의하지만 인스턴스 생성은 자신의 서브클래스이 하도록 함
2. 활용
- 추상 객체에서 어떤 객체를 생성(포함)해야하는지 모르기 때문에 객체의 생성을 서브 클래스가 하도록 미룬다.
문서와 어플리케이션의 예시
- Application 객체는 Document 객체를 새로 만들거나(New) 기존에 있던 것을 여는(Open) 연산을 한다.
- NewDocument()의 함수 예시에서 Document* doc = CreateDocument();와 같이 CreateDocument() 함수를 이용하여 Document를 생성할 수 있다.
- 만약 추상적 Document를 생성하는 것이 아니라 회계를 위한 장부, 회의록, 일정 등등 Document의 서브 클래스를 생성해야할 때, 구체적인 클래스를 명시해야 하는데 이를 Application 객체는 모두를 포괄할 수 없다.
- 따라서 구체적인 Document 인스턴스를 생성하는 것을 자신의 서브 클래스(MyApplication0)에 위임하고 서브 클래스는 어떤 Document를 생성할 것인지를 결정하도록 한다.
- 서브 클래스에게 구체 클래스를 생성하도록 위임하는 메서드 CreateMethod()를 팩토리 메서드라고 한다.
3. 참여자
- Product: 팩토리 메서드가 생성하는 객체의 인터페이스
- ConcreteProduct: Product 클래스를 자세히 구현하는 서브 클래스
- Creator: Product 타입의 객체를 반환하느 팩토리 메서드를 선언
- ConcreteCreator: Creator의 서브 클래스로 팩토리 메서드를 재정의 하여 ConcreteProduct 인스턴스를 반환
4. 협력 방법
- Creator는 팩토리 메서드를 통해 자신의 서브 클래스가 ConcreteProduct의 인스턴스를 반환하게 한다.
5. 결과
- 서브 클래스에대한 훅 메서드를 제공
- 팩토리 메서드를 통해 여러가지 버전의 ConcreteProduct를 반환하는 서브 클래스를 만들 수 있다.
- 병렬적인 클래스 계통을 연결 (수퍼 클래스가 서로 다른 두 서브 클래스를 연결)
- 단점: 사용자가 ConcreteProduct를 하나만 생성하는 데에도 ConcreteCreator를 만들어야 할 수 도 있다.
- 클래스 계통 연결
- 예시로 Figure객체와 Manipulator 객체를 분리하여 조작 대상과 조작 행위를 구분하여 추상화 한다.
- 다양한 Figure마다 조작 방식이 다르고 이에 필요한 정보를 Figure가 모두 가지고 있을 필요가 없기 때문이다.
- 이 때 LineFigure와 LineManipulator는 서로 다른 수퍼 클래스를 가지고 있지만 LineFigure 내에서 팩터리 메서드를 통해 LineManipulator를 생성하고 자신에 대한 관리를 수행할 수 있게 하여 같은 계통으로 연결할 수 있게 된다.
6. 구현
1) 크게 두 가지의 구현 방법
- Creator 클래스를 추상 클래스로 정의하고 팩토리 메서드에 대한 구현은 제공하지 않음.
-> 반드시 서브 클래스에서 팩토리 메서드를 재정의 해야함.
- Creator가 구체 클래스이고 팩토리 메서드에 대한 기본 구현을 제공함.
2) 팩토리 메서드를 매개변수화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Creator{
public:
virtual Product* Create(ProductId);
Product* Create(ProductId id)
{
if(id == PRODUCT_VER_THREE) return new iProduct;
if(id == PRODUCT_VER_FOUR) return new jProduct;
if(id == PRODUCT_VER_FIVE) return new kProduct;
// default product
return new Product;
}
};
|
cs |
- Creator 클래스의 팩토리 메서드에 어떤 클래스의 객체를 생성할 것 인지 매개변수로 전달함.
- Creator 클래스를 서브 클래싱함으로 매개변수에 대한 동작방식을 또 다양하게 할 수 있다. (업데이트 기능)
3) 언어마다 구현 방법이 다름
- 스몰토크는 Creator의 생성자에 직접 생성할 클래스를 멤버 변수로 저장하거나 C++에서는 순수 가상 함수로만 이루어진 추상 클래스로 구현할 수 있음.
- 접근자를 이용한 객체 생성: 접근을 할 때 객체를 생성, 객체가 생성되었는 지 확인하고 초기화를 함 (lazy initialization)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Creator {
public:
Product* GetProduct();
protected:
virtual Product* CreateProduct();
private:
Product* _product;
};
Product* Creator::GetProduct()
{
// Lazy Initialization if (_product == 0)
_product = CreateProduct();
return _product;
}
|
cs |
4) 템플릿을 이용한 서브클래싱 회피
- Product를 하나만 생산해야 되는데 또 다른 서브클래스를 만들 기에는 더 복잡해진다.
- Creator의 서브 클래스이자 템플릿 매개변수로 하여금 원하는 Product 클래스를 생산하도록 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Creator {
public:
virtual Product* CreateProduct() = 0;
};
template <class TheProduct>
class StandardCreator : public Creator {
public:
virtual Product* CreateProduct();
};
template<class TheProduct>
Product* StandardCreator<TheProduct>::CreateProduct() {
return new TheProduct;
}
/* 사용 예시
StandardCreator<MyProduct> myCreator;
Product* myProduct = myCreator.CreateProduct();
*/
|
cs |
5) 명명 규칙
- 생산하는 객체의 클래스를 명시하여 명명한다.
- Product* DoMakeMyProduct()
예제 코드
MazeGameCreator라는 클래스에서 Maze, Room, Wall, Door 객체를 직접 반환하는 팩터리 메서드를 정의한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include "Maze.h"
class MazeGameCreator
{
public:
Maze* CreateMaze();
// Factory Methods
virtual Maze* MakeMaze() const
{
return new Maze;
}
virtual Room* MakeRoom(int n) const
{
return new Room(n);
}
virtual Wall* MakeWall() const
{
return new Wall;
}
virtual Door* MakeDoor(Room* r1, Room* r2)const
{
return new Door(r1, r2);
}
};
|
cs |
MazeGameCreator의 CreateMaze() 함수 내에서 팩토리 메서드를 이용해 객체를 생성하는 것을 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include "MazeGameCreator.h"
Maze* MazeGameCreator::CreateMaze()
{
// 팩토리 메서드를 이용한 객체 생성
Maze* aMaze = MakeMaze();
Room* r1 = MakeRoom(1);
Room* r2 = MakeRoom(2);
Door* aDoor = MakeDoor(r1, r2);
aMaze->AddRoom(r1);
aMaze->AddRoom(r2);
r1->SetSide(North, MakeWall());
r1->SetSide(South, aDoor);
r1->SetSide(South, MakeWall());
r1->SetSide(West, MakeWall());
r2->SetSide(North, MakeWall());
r2->SetSide(South, MakeWall());
r2->SetSide(South, MakeWall());
r2->SetSide(West, aDoor);
return aMaze;
}
|
cs |
MazeGameCreator를 서브 클래싱하여 폭탄이 설치된 미로라는 새로운 미로를 만들 수 도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class BombedMazeGameCreator : MazeGameCreator
{
public:
BombedMazeGameCreator();
virtual Wall* MakeWall() const
{
return new BombedWall;
}
virtual Door* MakeRoom(int n) const
{
return new RoomWithBomb(n);
}
};
|
cs |
- 예제 코드에서도 볼 수 있 듯이 팩터리 메소드는 미로 객체를 구성하는 요소를 생산하는 데 쓰였다.
- 기존 추상 팩토리 패턴에서 팩토리 객체를 전달받아 객체를 생산하게 되는데 추상 팩토리 객체에서 팩터리 메소드를 정의하여 각 요소를 생산하는데 쓸 수 있다.
- 따라서 추상 팩토리에서도 팩터리 메소드가 많이 쓰인다.
참고 자료 : GoF 디자인 패턴
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 구조 패턴 (1) 적응자 Adapter (0) | 2021.01.26 |
---|---|
[디자인 패턴] 생성 패턴 (5) 싱글톤 Singleton (2021.02.19 수정) (0) | 2021.01.25 |
[디자인 패턴] 생성 패턴 (4) 원형 Prototype (0) | 2021.01.25 |
[디자인 패턴] 생성 패턴 (1) 추상 팩토리 Abstract Factory (0) | 2021.01.24 |
[디자인 패턴] 생성 패턴 (2) 빌더 Builder (0) | 2021.01.24 |