디자인 패턴/GoF

[디자인 패턴] 생성 패턴 (3) 팩토리 메서드 Factory Method

로파이 2021. 1. 24. 22:52

팩토리 메서드 (Factory Method)

 

1. 의도

  • 객체를 생성하기 위한 인터페이스를 정의하지만 인스턴스 생성은 자신의 서브클래스이 하도록 함

2. 활용

  • 추상 객체에서 어떤 객체를 생성(포함)해야하는지 모르기 때문에 객체의 생성을 서브 클래스가 하도록 미룬다.

문서와 어플리케이션의 예시

 

Document - Application

  • Application 객체는 Document 객체를 새로 만들거나(New) 기존에 있던 것을 여는(Open) 연산을 한다.
  • NewDocument()의 함수 예시에서 Document* doc = CreateDocument();와 같이 CreateDocument() 함수를 이용하여 Document를 생성할 수 있다. 
  • 만약 추상적 Document를 생성하는 것이 아니라 회계를 위한 장부, 회의록, 일정 등등 Document의 서브 클래스를 생성해야할 때, 구체적인 클래스를 명시해야 하는데 이를 Application 객체는 모두를 포괄할 수 없다.
  • 따라서 구체적인 Document 인스턴스를 생성하는 것을 자신의 서브 클래스(MyApplication0)에 위임하고 서브 클래스는 어떤 Document를 생성할 것인지를 결정하도록 한다. 
  • 서브 클래스에게 구체 클래스를 생성하도록 위임하는 메서드 CreateMethod()를 팩토리 메서드라고 한다.

3. 참여자

생성 패턴 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 디자인 패턴