팩토리 메소드 패턴 (Factory Methods Pattern) |
객체 생성을 담당하는 공장(Factory 클래스)을 만들어서 Factory와 하위클래스에서 객체생성을 대신하게 만드는 패턴
Factory 클래스에는 객체 생성의 전과 후에 필요한 처리를 담당하고 생성부분은 인터페이스만 제공하고
하위 클래스에서 해당 인터페이스에 생성 세부사항을 구현해서 어떤 객체를 어떻게 생성할지 결정하게 된다.
그러므로 객체생성 파트가 상속되어 있다면 팩토리 패턴이구나 생각하면 된다.
사용자 입장에서는 객체 생성을 일반적인 생성자나 new 키워드로 생성하고 끝내는게 아니라
객체를 만들고 그 객체로 팩토리 메소드를 호출하는것까지 하는것까지가 객체 생성과정이다.
사용하는 이유
직접 생성자를 호출하거나 new 키워드를 사용해도 되는데 굳이 과정을 한번 더 만드는 이유는
1) 객체간의 결합도를 낮추기 위해
- 결합도란 클래스에 변경할 부분이 생겼을때 다른 클래스에도 영향을 주는 정도를 의미
2) 객체 생성의 책임을 서브클래스에 위임시키고 서브클래스에 대한 정보를 은닉하고자 할 때
- 혹은 생성할 객체를 기술하는 책임을 서브클래스에게 정의하고자 할 때
3) 유지보수 및 확장에 용이해짐
- 만약 팩토리 클래스 없이 클라이언트 클래스에서 생성자 내에서 모든 경우를 처리한다면 새로운 객체 생성기능을 추가하려면 클래스의 생성자 내에 if문을 추가하거나 새로운 생성자를 오버로딩 해야하므로 기존의 코드를 자꾸 변경해야 함
4) 기존 객체를 재구성하는 대신 개존 객체를 재사용해서 리소스를 절약하고자 할때
- 객체 생성 방식이 바뀌면 관련 모든 코드를 수정해야하는 문제가 생길 수 있는데 이걸 객체 생성 팩토리로 따로 분리함으로써 객체 생성의 변화에 대비를 할 수 있음
5) 생성할 객체 타입을 예측할 수 없을 때
팩토리 메서드 vs 추상 팩토리 |
팩토리 메소드 패턴을 배우기 전에 팩토리 메서드 패턴과 추상 팩토리 패턴을 구분해야 한다는 사실을 인지해야 한다.
둘 다 객체를 생성하는 팩토리 패턴이지만 엄연히 전혀 다른 역할을 한다.
둘을 동시에 사용할수도 있고, 목적 또한 다르며 상하관계가 아닌 개별의 패턴이므로 상황에 따라 적절히 선택해야 한다.
팩토리 메서드 패턴 | 추상 팩토리 패턴 | |
공통점 | 팩토리 객체를 통해 객체 생성 과정을 추상화한 인터페이스만 제공하여 객체 생성을 캡슐화하여 구체적인 타입을 감추고 공장 클래스와 제품 클래스를 나눠 느슨한 결합구조를 표방 |
|
차이점 | 객체 생성 전/후에 해야할 일의 공통점에 초점 | 생성해야할 객체 집합군의 공통점에 초점 |
구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는것이 목적 | 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적 | |
한 Factory당 한 종류의 객체 생성 지원 | 한 Factory에서 서로 연관된 여러 종류의 객체 생성 지원 | |
메소드 레벨에 포커스 | 클래스 레벨에 포커스 |
팩토리 메소드 패턴 구조 |
좌측의 Creator클래스와 ConcreteCreator 클래스는 객체 생성을 맡는 공장(Factory)역할이고
우측의 Product클래스와 ConcreteProduct 클래스가 생성될 객체의 행동을 정의하는 부분이다.
1) Creator : 최상위 공장클래스이며 팩토리 메서드를 추상화해서 인터페이스로 제공함
인터페이스는 정의하지만, 어떤 클래스의 객체를 생성할지에 대한 결정은 서브클래스에서 정함
- templateMethod(객체 생성 처리 메서드) :
객체 생성에 관한 전/후처리를 템플릿화한 것, 일반적인 생성자에서 전/후처리 하는것처럼 만들면 됨
- createProduct(팩토리 메서드) :
서브 공장클래스에서 재정의할 객체생성부분을 abstract로 구현해서 인터페이스를 만드는 것
2) ConcreteCreator : 각 서브 공장클래스들은 각각 맞는 제품 객체를 반환하도록 인터페이스를 구체적으로 재정의
제품 하나당 해당 제품에 맞는 생산 공장이 있는 것
3) Product : 제품 구현체를 추상화해서 인터페이스로 만든 것
4) ConcreteProduct : 제품 구현체
구현 코드 예시 |
1) 예시 코드
#include <iostream>
#include <fstream>
#include <map>
using namespace std;
/*
Product 클래스, 일반 객체의 역할
*/
class Document {
public:
virtual bool Open(const char* pFileName) = 0;
};
class HwpDocument : public Document {
public:
bool Open(const char* pFileName) {
ifstream ifs(pFileName);
if (!ifs)
return false;
// -- HWP 고유 프로세싱
return true;
}
};
class MsWordDocument : public Document {
public:
bool Open(const char* pFileName) {
ifstream ifs(pFileName);
if (!ifs)
return false;
// -- MS-Word 고유 프로세싱
return true;
}
};
/*
Creator 클래스, 팩토리의 역할
*/
class Application {
public:
void NewDocument(const char* pFileName) { // 이 함수가 팩토리 메서드
Document* pDoc = CreateDocument(); // 바로 밑에 추상함수로 선언된것 주목
docs_[pFileName] = pDoc;
pDoc->Open(pFileName);
}
virtual Document* CreateDocument() = 0;
private:
map<string, Document*> docs_;
};
class HwpApplication : public Application {
public:
Document* CreateDocument() {
return new HwpDocument;
}
};
class MsWordApplication : public Application {
public:
Document* CreateDocument() {
return new MsWordDocument;
}
};
int main()
{
HwpApplication hwp; // 클라이언트는 그냥 평소처럼 생성자를 통해 생성
hwp.NewDocument("input.hwp"); // 48번줄의 클래스의 객체가 NewDocument함수 호출
// 그럼 HwpApplication클래스는 상위클래스의 NewDocument로 객체 생성
/*
객체를 받아서 또 사용해야 한다면
팩토리의 생성자가 pDoc를 return하게 하고 아래처럼 사용하면 된다.
HwpApplication hwp_factory; // 팩토리 생성
HwpDocument hwp = hwp_factory.NewDocument("input.hwp"); // 객체를 팩토리를 이용해 생성
hwp.func(); // 객체를 이용해서 추후 작업
*/
}
2) 설명
- 팩토리 부모클래스에는 생성에 관한 전/후처리만 구체적으로 작성하고 생성함수는 가상함수로 작성
- 팩토리 자식클래스에서는 부모클래스를 상속받아서 개별적으로 적용되어야 할 함수를 override하고 해당 클래스에 맞게 생성 코드를 작성
- 사용자는 객체 생성 호출을 그냥 생성자로 `A a;`하고 끝내거나 `A* a = new A();`같은걸로 끝내는게 아니고
팩토리 메서드를 호출하고, 해당 팩토리 메서드가 객체 생성의 과정을 담당한다.
A a;
a.factoryMethod();
장단점 |
1) 장점
- 생성자(Creator)와 구현 객체(concrete product)의 강한 결합을 피할 수 있음
- 팩토리 메소드를 통해 객체 생성 후 공통으로 할 일을 수행하도록 지정할 수 있음
- 캡슐화, 추상화를 통해 생성되는 객체의 구체적인 타입을 감출 수 있음
- 단일 책임 원칙 준수(객체 생성 코드를 팩토리 클래스 한곳에 둬서 유지보수 하기 쉽게 하는 것)
- 개방/폐쇄 원칙 준수(기존 코드의 수정없이 새로운 클래스를 추가로 기능을 도입할 수 있는 것)
- 생성에 대한 인터페이스와 생성에 대한 구현이 나뉘어져 있기 때문에 여러 개발자가 협업을 통해 개발 가능
2) 단점
- 코드의 복잡성 증가
- 각 제품마다 팩토리 객체를 모두 구현해줘야 하므로, 구현체가 늘어나면 팩토리 클래스의 서브클래스 수가 엄청나게 많아진다.
※ 참고 문헌
https://refactoring.guru/ko/design-patterns/what-is-pattern
https://wikidocs.net/52073#factory-methods