디자인 패턴/GoF

[디자인 패턴] 생성 패턴 (4) 원형 Prototype

로파이 2021. 1. 25. 14:17

원형 (Prototype) 패턴

 

1. 의도

  • 객체를 복사하여 사용하는 것. 복사할 객체의 클래스를 명시한다.

2. 활용

  • 인스턴스화할 클래스를 런타임에 지정할 때
  • 팩토리 메서드에서 객체 합성으로 클래스 계통을 연결 하였는 데 이를 피하고 싶을 때
  • 원형 복제를 먼저하고 나중에 초기화 하여 자세히 설정된 객체를 사용하고 싶을 때
  • 주로 인스턴스의 상태가 유한한 경우 복제하여 초기화하는 것이 유용할 수 있다.

Graphic 객체와 Graphic Tool 객체

원형 패턴 예시

팩토리 메서드에서 기능 분리 등으로 서브 클래스에서 객체를 합성하여 인스턴스를 생성하였다. 하지만 생성하는 클래스가 다를 경우 팩터리 메서드를 상속하는 ImageGraphicTool, WidgetGraphicTool... 등 새로운 서브클래스를 만들어야 했는데 이는 기존 Tool 기능에 관한 프레임워크 구조가 복잡해질 수 있다. 특히 생성하는 클래스들을 추상화하여 사용할 수 있을 때, 이들의 원형을 복제하여 인스턴스를 만들어 사용하면 GraphicTool을 상속받는 새로운 클래스를 만들지 않고도 Graphic 객체를 다룰 수 있다

 

3. 참여자

생성 패턴 - Prototype

  • Prototype: 자신을 복제하는 인터페이스를 제공하고 모든 복제 가능한 객체의 추상 클래스
  • ConcretePrototype: 자신을 복제 하고 상태를 바꾸기 위한 Set 함수를 제공
  • Client: 원형을 복제하여 사용하는 대상

4. 협력방법

  • 사용자는 원형을 복제하여 객체를 생성한다.

5.결과

 

추상 팩토리 및 빌더와 마찬가지로 사용자 쪽에서 구체적인 제품을 알 필요가 없어진다.

원형에서 중요한 것은 객체를 표현하는 내부 값을 이용하여 새로운 클래스를 만드는 것을 줄인다는 것이다.

ex) 음표 -> 기본 4분 음표에서 길이, 픽셀값, 위치 등을 수정하여 여러 음표를 만들어낼 수 있다.

 

  • 원형을 제공하는 클래스를 추가하는 것만으로 시스템에 새로운 제품을 추가할 수 있다.
  • 새로운 클래스를 정의하지 않고 내부 값(state)를 다르게하여 새로운 객체를 생성한다.
  • 내부 값 종류를 더 다양하게 하여 새로운 객체를 만들 수 있다.
  • 그러므로 서브 클래스 수를 줄인다. 팩토리 메서드에서 봤던 Creator를 상속하는 새로운 서브 클래스를 만들 필요가 없어진다.
  • 런타임에 클래스를 등록하여 사용할 수 있다.

6. 구현

 

1) 원형 관리자를 사용

 

원형 관리자라는 레지스트리에서 원형을 검색하여 복제된 인스턴스를 반환.

 

2) Clone() 연산을 구현

 

원본과 같은 내용을 가지는 객체를 복사하는 연산.

C++과 같은 언어에서 얇은 복사를 피하고 깊은 복사를 수행할 수 있게 구현한다.

 

3) Clone을 초기화

 

객체 내부 상태를 변경함으로 다양한 객체를 만들어내기에 setXXX()와 같은 설정 함수를 통해 복제된 인스턴스를 초기화한다.

 

 


예제코드

 

기존 추상 팩토리에서 상속하여 원형 패턴을 적용한 MazePrototpyeFactory를 설계해보자

 

멤버함수에 복제할 원형의 원본을 생성자를 통해 초기화 해주고

구성요소를 생산하는 메서드에서 원본을 복제하여 인스턴스를 반환해주면 된다.

#include "MazeFactory.h"
class MazePrototypeFactory : public MazeFactory
{
public:
	// 구성요소의 원형으로 초기화
	MazePrototypeFactory(Maze*, Wall*, Room*, Door*) {}

	virtual Maze* MakeMaze() const;
	virtual Room* MakeRoom(int) const;
	virtual Wall* MakeWall() const;
	virtual Door* MakeDoor(Room*, Room*) const;
private:
	Maze* _prototypeMaze;
	Room* _prototypeRoom;
	Wall* _prototypeWall;
	Door* _prototypeDoor;
};
#include "MazePrototypeFactory.h"

MazePrototypeFactory::MazePrototypeFactory(Maze* m, Wall* w, Room* r, Door* d) {
    // 원본을 초기화
    _prototypeMaze = m;
    _prototypeWall = w;
    _prototypeRoom = r;
    _prototypeDoor = d;
}

Door* MazePrototypeFactory::MakeDoor(Room* r1, Room* r2) const
{
    Door* door = _prototypeDoor->Clone();
    door->Initialize(r1, r2);
    return door;
}

Wall* MazePrototypeFactory::MakeWall() const
{
    return _prototypeWall->Clone();
}

Maze* MazePrototypeFactory::MakeMaze() const
{
    return _prototypeMaze->Clone();
}

Room* MazePrototypeFactory::MakeRoom(int n) const
{
    Room* room = _prototypeRoom->Clone();
    room->SetNumber(n);
    return room;
}

MakeDoor(Room*, Room*) 메서드에서 Door 객체의 Clone() 함수를 호출하였는데

이를 Door 클래스에서 구현해야한다. 아래 예시에서는 복사 생성자를 이용하여 Clone 함수 구현을 제시한다.

class Door : public MapSite
{
private:
	// 상태
	bool _isOpen;
	// 연결하는 두 방
	Room* _room1;
	Room* _room2;
public:
	// 문을 초기화하기 위해서는 문이 어느 방 사이에 있는지 알아야한다.
	Door();
	Door(const Door&);
	virtual void Initialize(Room*, Room*);
	virtual Door* Clone() const;
	virtual void Enter();
	Room* OtherSideFrom(Room*);
};
#include "Door.h"

Door::Door()
{
    _room1 = nullptr;
    _room2 = nullptr;
}
// 복사 생성자
Door::Door(const Door& other)
{
    _room1 = other._room1;
    _room2 = other._room2;
}
// 초기화 연산 (Set 메서드)
void Door::Initialize(Room* r1, Room* r2)
{
    _room1 = r1;
    _room2 = r2;
}

Door* Door::Clone() const
{
    // 복사 생성자 호출
    return new Door(*this);
}