본문 바로가기
개발/Architecture & Design Pattern

C++ - 팩토리 메서드 패턴 (Factory Method Pattern)

by 피로물든딸기 2024. 2. 12.
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 클래스 다이어그램 그리기

- 스마트 포인터 : unique_ptr

- 의존 역전 원칙

 

팩토리 메서드 패턴 (Factory Method Pattern) - 생성 패턴

- 객체를 생성하는 과정을 서브 클래스에서 결정할 수 있도록 하는 패턴

- 객체 생성에 대한 구체적인 구현을 서브 클래스로 미룬다.

- 클라이언트는 객체 생성에 대한 구체적인 클래스를 알 필요 없이, 추상 클래스를 통해 인터페이스에만 의존할 수 있다.

 

구현

- 객체 생성을 처리하는 메서드가 있는 인터페이스나 추상 클래스를 생성한다.

- 팩터리 메서드가 생성할 객체의 구상 클래스를 만든다.

- 인터페이스나 추상 클래스를 구현하여 팩토리 클래스를 만든다.

  이 클래스에 객체를 생성하는 메서드가 포함되며, 이 메서드는 인터페이스나 추상 클래스의 인스턴스를 반환한다.

- 클라이언트는 팩토리 클래스의 메서드를 호출하여 객체를 생성한다.

 

장점

- SRP(단일 책임 원칙), 제품 생성 코드를 한 곳에서만 관리하여 유지보수가 쉽다.

- OCP(개방-폐쇄 원칙), 객체 생성 코드를 변경하거나 확장할 때, 기존 코드를 수정하지 않고 새로운 제품을 추가한다.

- DIP(의존 역전 원칙), 객체 생성을 위한 팩토리가 추상화되어 있어 구체적인 클래스에 의존되지 않는다.

- 클라이언트는 구체적인 클래스에 의존하지 않고 추상 클래스에 의존하므로 유연성이 높아진다.

 

단점

- 클래스의 수가 늘어날 수 있고, 클래스 간의 관계가 복잡해진다.

- 각각의 Concrete Creator 클래스마다 Concrete Product를 생성하는 메서드를 구현하므로 코드 중복이 발생한다.


여러 가지 빵을 만드는 빵 가게

 

빵은 여러 단계를 거쳐서 만들어진다. 

여기서는 편의상 step 1 ~ 3 단계 후에 빵이 만들어진다고 가정하자.

class Bread
{
public:
	Bread() = default;
	virtual ~Bread() = default;
	virtual void step1() const { cout << "step 1 of making : " << name << endl; }
	virtual void step2() const { cout << "step 2 of making : " << name << endl; }
	virtual void step3() const { cout << "step 3 of making : " << name << endl; }
	string getName() const { return name; }
protected:
	string name = "Bread";
};

 

빵 가게는 피자빵, 머핀, 크로와상, 베이글 등을 만들어서 판매한다.

빵은 makeBread 메서드 내에서 빵 이름(name)에 따라 new 연산자로 만들어진다.

class BreadStore
{
public:
	BreadStore() = default;
	Bread* makeBread(string name)
	{
		Bread* bread;

		if (name == "pizzabread") bread = new PizzaBread();
		if (name == "muffin") bread = new Muffin();
		// or croissant, bagel, ...

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}
};

 

그런데 새로운 빵이 계속 추가되거나, 인기가 없는 빵은 사라지는 경우

BreadStore makeBread 계속해서 고쳐야 한다.

	if (name == "pizzabread") bread = new PizzaBread();
	if (name == "muffin") bread = new Muffin();
	// or croissant, bagel, ...

빵 공장 클래스

 

빵을 만드는 코드를 따로 캡슐화하여 빵 공장 클래스를 만들자.

class BreadFactory
{
public:
	Bread* createBread(string name)
	{
		if (name == "pizzabread") return new PizzaBread();
		if (name == "muffin") return new Muffin();
		// or croissant, bagel, ...
        
		return nullptr;
	}
};

 

빵 가게 코드는 아래와 같이 변경된다.

빵이 추가되더라도 BreadStore는 더 이상 고칠 필요가 없고 팩토리에서 빵을 추가하면 된다.

class BreadStore
{
public:
	BreadStore() = default;
	BreadStore(BreadFactory* f) : factory(f) {}
	Bread* makeBread(string name)
	{
		Bread* bread = factory->createBread(name);

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

private:
	BreadFactory* factory = nullptr;
};

 

이제 빵 가게(클라이언트)는 빵 공장을 이용해서 빵 인스턴스를 만들게 된다.

빵 공장이 직접적으로 Bread 구상 클래스를 참조한다.

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>

using namespace std;

class Bread
{
public:
	Bread() = default;
	virtual ~Bread() = default;
	virtual void step1() const { cout << "step 1 of making : " << name << endl; }
	virtual void step2() const { cout << "step 2 of making : " << name << endl; }
	virtual void step3() const { cout << "step 3 of making : " << name << endl; }
	string getName() const { return name; }

protected:
	string name = "Bread";
};

class PizzaBread : public Bread
{
public:
	PizzaBread() { name = "Pizza Bread"; }
};

class Muffin : public Bread
{
public:
	Muffin() { name = "Muffin"; }
};

class BreadFactory
{
public:
	Bread* createBread(string name)
	{
		if (name == "pizzabread") return new PizzaBread();
		if (name == "muffin") return new Muffin();
		// or croissant, bagel, ...
        
		return nullptr;
	}
};

class BreadStore
{
public:
	BreadStore() = default;
	BreadStore(BreadFactory* f) : factory(f) {}
	Bread* makeBread(string name)
	{
		Bread* bread = factory->createBread(name);

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

private:
	BreadFactory* factory = nullptr;
};

int main(void)
{
	BreadFactory* factory = new BreadFactory();
	BreadStore* breadStore = new BreadStore(factory);
	Bread* bread = breadStore->makeBread("muffin");

	return 0;
}

 

효율적인 메모리 관리를 위해 unique_ptr을 적용한 코드는 아래와 같다.

#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Bread
{
public:
	Bread() = default;
	virtual ~Bread() = default;
	virtual void step1() const { cout << "step 1 of making : " << name << endl; }
	virtual void step2() const { cout << "step 2 of making : " << name << endl; }
	virtual void step3() const { cout << "step 3 of making : " << name << endl; }
	string getName() const { return name; }
protected:
	string name = "Bread";
};

class PizzaBread : public Bread
{
public:
	PizzaBread() { name = "Pizza Bread"; }
};

class Muffin : public Bread
{
public:
	Muffin() { name = "Muffin"; }
};

class BreadFactory
{
public:
	unique_ptr<Bread> createBread(string name)
	{
		if (name == "pizzabread") return make_unique<PizzaBread>();
		if (name == "muffin") return make_unique<Muffin>();
		// or croissant, bagel, ...
        
		return nullptr;
	}
};

class BreadStore
{
public:
	BreadStore() = default;
	BreadStore(unique_ptr<BreadFactory> f) : factory(move(f)) {}
	unique_ptr<Bread> makeBread(string name)
	{
		unique_ptr<Bread> bread = factory->createBread(name);

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

private:
	unique_ptr<BreadFactory> factory = nullptr;
};

int main(void)
{
	unique_ptr<BreadFactory> factory = make_unique<BreadFactory>();
	BreadStore* breadStore = new BreadStore(move(factory));
	unique_ptr<Bread> bread = breadStore->makeBread("muffin");

	return 0;
}

팩토리 메서드 패턴

 

팩토리 메서드 패턴에서는 추상 클래스인 Creator가 객체를 만들기 위한 팩토리 메서드 인터페이스를 제공한다.

그리고 실제 팩토리 메서드를 구현하고 객체(Product)를 만드는 것은 서브 클래스ConcreteCreator가 된다.

ConcreteProductProduct 인터페이스를 구현해야 Creator모든 제품 객체를 참조할 수 있다.

 

위의 클래스 다이어그램을 참고하여 빵 가게에 팩토리 메서드 패턴을 적용해 보자.

 

이제 빵 가게 호황으로 브랜드를 런칭하기로 했다. ( → 파리바게뜨뚜레쥬르, ...)

 

BreadStore에서 createBread추상 메서드로 변경한다.

여기서 createBreadBread 인스턴스를 만드는 팩토리 메서드가 된다.

class BreadStore
{
public:
	virtual ~BreadStore() = default;

	unique_ptr<Bread> makeBread(string name)
	{
		unique_ptr<Bread> bread = createBread(name);

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

protected:
	virtual unique_ptr<Bread> createBread(string name) = 0;
};

 

팩토리 메서드는 추상 메서드로 선언하여 서브 클래스에서 객체 생성을 책임지도록 한다.

그리고 이 메서드가 특정 제품(Bread)를 리턴하게 된다.

createBread는 클라이언트에서 실제로 생성되는 구상 객체가 무엇인지 알 수 없게 한다.

 

 

예를 들어 파리바게뜨는 아래와 같이 팩토리 메서드를 구현할 수 있다.

class ParisBaguette : public BreadStore
{
protected:
	unique_ptr<Bread> createBread(string name) override
	{
		if (name == "pizzabread") return make_unique<ParisBaguettePizzaBread>();
		if (name == "muffin") return make_unique<ParisBaguetteMuffin>();
		// or croissant, bagel, ...

		return nullptr;
	}
};

 

이제 제품을 생산하는 생산자 클래스제품 클래스로 나뉘어진다.

BreadStoreBread 모두 추상 클래스고, 자식 클래스가 추상 클래스를 확장한다.

 

또한 파리바게뜨 피자빵에 대한 여러 요구 사항을 따로 구현할 수 있다.

예를 들어 파리바게뜨의 피자빵은 step1에서 파리바게뜨 만의 특수한 방법이 추가된다. (step1 override)

class ParisBaguettePizzaBread : public Bread
{
public:
	ParisBaguettePizzaBread() { name = "ParisBaguette Pizza Bread"; }
	void step1() const override { cout << "step 1 of ParisBaguette making : " << name << endl; }
};

 

파리바게뜨는 다음과 같이 빵을 만들게 된다.

	unique_ptr<BreadStore> parisBaguette = make_unique<ParisBaguette>();
	unique_ptr<Bread> bread1 = parisBaguette->makeBread("pizzabread");
	bread1 = parisBaguette->makeBread("muffin");

 

클래스 다이어그램으로 요약하면 다음과 같다.

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>
#include <memory>

using namespace std;

/* ------------------------- Bread ------------------------- */

class Bread
{
public:
	Bread() = default;
	virtual ~Bread() = default;
	virtual void step1() const { cout << "step 1 of making : " << name << endl; }
	virtual void step2() const { cout << "step 2 of making : " << name << endl; }
	virtual void step3() const { cout << "step 3 of making : " << name << endl; }
	string getName() const { return name; }
protected:
	string name = "Bread";
};

class ParisBaguettePizzaBread : public Bread
{
public:
	ParisBaguettePizzaBread() { name = "ParisBaguette Pizza Bread"; }
	void step1() const override { cout << "step 1 of ParisBaguette making : " << name << endl; }
};

class ParisBaguetteMuffin : public Bread
{
public:
	ParisBaguetteMuffin() { name = "ParisBaguette Muffin"; }
};

class TouslesJoursPizzaBread : public Bread
{
public:
	TouslesJoursPizzaBread() { name = "TouslesJours Pizza Bread"; }
};

class TouslesJoursMuffin : public Bread
{
public:
	TouslesJoursMuffin() { name = "TouslesJours Muffin"; }
	void step2() const override { cout << "step 2 of TouslesJoursMuffin making : " << name << endl; }
};

/* ------------------------- Store ------------------------- */

class BreadStore
{
public:
	virtual ~BreadStore() = default;

	unique_ptr<Bread> makeBread(string name)
	{
		unique_ptr<Bread> bread = createBread(name);

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

protected:
	virtual unique_ptr<Bread> createBread(string name) = 0;
};

class ParisBaguette : public BreadStore
{
protected:
	unique_ptr<Bread> createBread(string name) override
	{
		if (name == "pizzabread") return make_unique<ParisBaguettePizzaBread>();
		if (name == "muffin") return make_unique<ParisBaguetteMuffin>();
		// or croissant, bagel, ...

		return nullptr;
	}
};

class TouslesJours : public BreadStore
{
protected:
	unique_ptr<Bread> createBread(string name) override
	{
		if (name == "pizzabread") return make_unique<TouslesJoursPizzaBread>();
		if (name == "muffin") return make_unique<TouslesJoursMuffin>();
		// or croissant, bagel, ...

		return nullptr;
	}
};

int main(void)
{
	unique_ptr<BreadStore> parisBaguette = make_unique<ParisBaguette>();
	unique_ptr<BreadStore> touslesJours = make_unique<TouslesJours>();

	unique_ptr<Bread> bread1 = parisBaguette->makeBread("pizzabread");
	cout << endl;
	bread1 = parisBaguette->makeBread("muffin");
	cout << endl;

	unique_ptr<Bread> bread2 = touslesJours->makeBread("muffin");
	cout << endl;
	bread2 = parisBaguette->makeBread("pizzabread");
	cout << endl;

	return 0;
}


의존 역전 원칙 (DIP, Dependency Inversion Principle)

 

의존 역전 원칙은 추상화 된 것에 의존하도록 만들어야 한다는 원칙이다.

상위 모듈은 하위 모듈에 의존해서는 안된다.

 

여기서 BreadStore의 Bread에 의해 정의되기 때문에 상위 모듈이고, Bread는 하위 모듈이다.

 

만약 BreadStore가 직접 PizzaBrea, Muffin, Croissant, Bagel, ... 등을 만들면 모든 빵 객체에 의존하게 된다.

이 경우, 각각의 Bread 클래스의 구현이 변경되면 Store도 수정해야 되는 경우가 발생한다.

(→ 빵집은 모든 빵 클래스 구현에 의존한다.)

 

왜냐하면 BreadStoremakeBread에서 직접적으로 모든 빵 인스턴스를 만들기 때문이다.

(아래 코드는 PizzaBread, Muffin, ... 과 의존적이다.)

	Bread* makeBread(string name)
	{
		Bread* bread;

		if (name == "pizzabread") bread = new PizzaBread();
		if (name == "muffin") bread = new Muffin();
		// or croissant, bagel, ...

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

 

따라서 팩토리 메서드 패턴으로 인스턴스를 만드는 부분을 createBread로 대체한다.

그러면 BreadStore는 오직 추상화된 Bread에만 의존하게 된다.

public:
	unique_ptr<Bread> makeBread(string name)
	{
		unique_ptr<Bread> bread = createBread(name);

		bread->step1();
		bread->step2();
		bread->step3();

		return bread;
	}

protected:
	virtual unique_ptr<Bread> createBread(string name) = 0;

 

이제 BreadStore에서 new로 만들었던 구상 클래스들은 모두 추상화된 Bread에만 의존하게 된다.

그리고 실제 Bread 구상 클래스도 추상화된 Bread에 의존하게 된다.

 

이전 대비 이후에 의존 화살표가 뒤집어져 있다는 것을 알 수 있다.

 

구상 클래스에 대한 레퍼런스를 특정 변수에 저장하지 않고,

구상 클래스에서 직접 유도된 클래스를 만들지 않도록 수정하여 의존 역전 원칙을 위배하지 않도록 하였다.


ClassDiagram.md

```mermaid
  classDiagram    
    class BreadStore {       
      createBread()*
      makeBread() 
    }
    class ParisBaguette { 
      createBread()
    }
    class TouslesJours { 
      createBread()      
    }
    class ParisBaguettePizzaBread { 
    }
    class ParisBaguetteMuffin {     
    }
    
    BreadStore <|-- ParisBaguette
    BreadStore <|-- TouslesJours  

    ParisBaguette --> ParisBaguetteMuffin
    ParisBaguette --> ParisBaguettePizzaBread  

    TouslesJours --> TouslesJoursMuffin
    TouslesJours --> TouslesJoursPizzaBread  
```
반응형

댓글