책정리/GoF 디자인 패턴

5장 대행 함수를 통한 객체 생성 문제(Factory Method 패턴)

GONII 2019. 1. 14. 22:37
객체를 생성하는 작업을 복잡하고 어렵거나 특정한 절차를 따라야 경우 이를 대행하는 함수를 있다.
  • 문제 사례 설명
윈도우 운영체제에서 문서 파일을 더블클릭한 경우를 생각해보자. 문서 파일 이름의 확장자에 따라 적절한 응용 프로그램이 실행될 것이며, 응용 프로그램은 문서 파일을 열어 내용을 화면 상에 보여줄 것이다. 예를 들어 파일 이름 확장자가 hwp 한글 소프트웨어가 실행되고, doc이면 마이크로소프트 워드가, zip이면 알집과 같은 압축 관련 소프트웨어가 실행될 것이다.
이런 동작 과정을 객체지향 관점에서 살펴보면 가지 종류의 객체가 생성되어야 함을 있다. 하나는 응용프로그램 객체이고, 다른 하나는 더블클릭된 문서파일에 대한 객체이다.
먼저 응용 프로그램 객체는 운영체제에 의해 생성되어야 한다는 것은 명백하다. 왜냐하면 오늘날의 컴퓨터 실행 환경은 모든 응용프로그램이 운영체제 상에서 실행되는 구조이며 운영체제가 응용프로그램을 실행하기 위해서는 미리 응용 프로그램 객체를 생성해야 하기 때문이다. 이를 위해 운영체제는 구체적인 응용프로그램에 상관없이 응용 프로그램 객체를 동일하게 다루기 위한 Application 같은 클래스 자료형이 필요할 것이다.
한편 문서 파일 객체는 응용프로그램에 의해 생성되어야 한다. 왜냐하면 문서 파일 객체가 운영체제에 의해 생성된다고 가정하면 운영체제가 문서 파일의 내용을 읽어들여 그것을 분석하고 문서 파일의 종류에 맞게 객체를 생성해야 하는데, 이런 방식이 적용될 경우에는 새로운 종류의 문서 파일이 만들어지면 거기에 맞는 문서 파일 객체를 생성하기 위해 운영체제를 변경시켜야 하는 문제가 발생하기 때문이다.
이상의 논의를 바탕으로 객체들간의 생성 실행 관계를 그려보면 [그림 5-1] 같다.

  • 다양한 접근 방법 및 FACTORY METHOD 패턴
주어진 문제를 한글과 마이크로소프트 워드 응용 프로그램으로 생각한다면 [그림 5-2] 유사한 클래스간 관계가 맺어져야 것이다.

여기서 문제는 운영체제가 불러줄 NewDocument() 인터페이스를 어떻게 배치하고, 어떻게 구현할 것인가 이다. 이때 NewDocument() 멤버 함수는 HwpApplication 클래스에 대해서는 HwpDocument 클래스의 객체를 생성하고, MsWordApplication 클래스에 대해서는 MsWordDocument 클래스의 객체를 생성해야 것이다.
  • 기본적인 방법: 클래스별 멤버 함수 구현 방식
각각의 응용 프로그램마다 고유의 객체를 생성하도록 만들기 위한 가지 해결 방식은 응용 프로그램 클래스마다 NewDocument() 멤버 함수를 구현하게 만드는 것이다.

[그림5-3] 클래스 구조에서 클래스별 NewDocument() 멤버 함수의 구현은 다음과 같은 형태가 것이다.
[소스 5-1] 클래스별 객체 생성 형태의 소스코드
// one2onefunc.c
#include <iostream>
#include <map>
using namespace std;
 
class Application
{
public :
virtual void NewDocument(char* pFileName) = 0;
};
 
class HwpApplication : public Application
{
void NewDocument(char* pFileName)
{
HwpDocument* pDoc = new HwpDocument;
docs_[pFileName] = pDoc;
pDoc->open(pFileName);
}
private :
map<string, HwpDocument*> docs_;
};
 
class MsWordApplication : public Application
{
void NewDocument(char* pFileName)
{
MsWordDocument* pDoc = new MsWordDocument;
docs_[pFileName] = pDoc;
pDoc->open(pFileName);
}
private:
map<string, MsWordDocument*> docs_;
};
[소스5-1]에서 HwpDocument, MsWordDocument 객체를 각각 new 연산자에 의해 직접 생성된다. 이처럼 각각의 응용 프로그램 객체가 직접 문서를 객체를 생성해줄 경우 일단 원하는 목적은 달성할 있다.
그러나 방식은 다시 생각해보면 다소 비효율적임을 있다. 비효율의 원인은 바로 HwpApplication 클래스와 MsWordApplication 클래스에서 구현한 NewDocument() 멤버 함수가 new 연산자를 호출하는 부분을 제외하고는 완전히 동일한데도 불구하고 각각 따로 구현되어 있기 때문이다.
같은 비효율성은 응용 프로그램의 개수가 많아질수록 증가할 것이다. 또한 더욱 문제는 NewDocument() 멤버 함수의 구현을 변경할 필요가 있을 경우에는 응용 프로그램 클래스의 구현 내용을 일일이 변경해야 하는 문제가 발생한다.
  • 패턴 활용 방법: FACTORY METHOD 패턴
문제의 원인은 응용 프로그램 클래스마다 NewDocument() 멤버 함수를 따로 따로 구현했다는 있다. 따라서 문제를 해결하기 위해서는 NewDocument() 멤버 함수의 구현을 곳으로 한정하는 밖에 없다. 이를 위한 가장 적합한 장소는 바로 Application 클래스가 것이다. 왜냐하면 Application 클래스는 모든 응용 프로그램 클래스를 대표할 있기 때문이다. 한편 응용 프로그램 클래스마다 구현하였던 NewDocument() 멤버 함수를 하나로 통합해서 구현하려면 응용 프로그램 클래스마다 구현 내용이 달랐던 문서 파일 객체 생성 부분을 응용 프로그램 클래스에 상관없이 동일하도록 수정해야 것이다.
이를 위한 좋은 방법으로는 문서 파일 객체 생성 부분을 별도의 멤버 함수로 정의하고 이를 NewDocument() 멤버 함수에서 불러 사용하게 만들어주는 것이다. , CreateDocument() 같이 문서 파일 객체 생성을 대행해주는 멤버 함수를 새로 정의하고 NewDocument() 멤버 함수에서는 이를 불러쓰게 만들어 주는 것이다. 그런데 이런 방법을 사용하자면 가지를 해결해야 한다. 하나는 CreateDocument() 멤버 함수에 의해 실제 생성되는 문서 파일 객체는 응용 프로그램 클래스마다 서로 달라야 한다는 것이고, 다른 하나는 CreateDocument() 멤버 함수에 의해 성성된 문서 파일 객체는 응용 프로그램 클래스에 상관없이 NewDocument() 멤버 함수 내에서는 동일하게 취급되어야 한다는 것이다.
우선 응용 프로그램 클래스마다 CreateDocument() 멤버 함수에 의해 생성되는 문서 파일 객체가 다르도록 만들어줄 있는 방법은 가지 뿐이다. 그것은 응용 프로그램 클래스마다 CreateDocument() 멤버 함수를 직접 구현하는 것이다. 물론 이때 응용 프로그램 클래스에서 구현하는 CreateDocument() 멤버 함수는 Application 클래스의 CreateDocument() 멤버 함수를 overriding해서 정의하는 형태여야 것이다. 왜냐하면 그렇게 해야지만 NewDocument() 멤버 함수에서 응용 프로그램 클래스마다 정의된 CreateDocument() 멤버 함수를 다형성을 이용해서라도 제대로 불러줄 있기 때문이다.
한편 응용 프로그램 클래스마다 서로 다른 문서 파일 객체를 생성하더라도 NewDocument() 멤버 함수에서는 이들을 동일하게 취급할 있기 위해서는 응용 프로그램 클래스에서 생성 가능한 모든 문서 파일 객체를 대표할 있는 별도의 클래스 정의가 필요할 것이다. 왜냐하면 CreateDocument() 멤버 함수에 의해 되돌려지는 객체의 자료형이 다를 경우 NewDocument() 멤버 함수는 하나로 통합해서 구현될 없기 때문이다. 따라서 NewDocument() 멤버 함수를 하나로 통합 구현할 있게 하려면 HwpDocument MsWordDocument 클래스의 상위에 Document 같은 클래스를 정의해서 NewDocument() 멤버 함수에서는 Document 자료형으로 생성된 문서 파일 객체를 다루도록 만들어주어야 것이다.

운영체제에서 호출하는 NewDocument() 인터페이스를 Application 클래스에서만 정의되어 있고, 내부 구현 필요한 문서 파일 객체를 생성하는 부분은 응용 프로그램 클래스의 CreateDocument() 멤버 함수로 정의되어 있음을 있을 것이다. 이렇게 함으로써 Application 하위 클래스는 클래스마다 NewDocument() 멤버 함수를 구현하지 않으면서도 각각의 응용 프로그램마다 고유의 문서 파일 객체를 생성할 있게 되는 것이다.
이처럼 객체를 생성하되 직접 객체 생성자를 호출해서 객체를 생성하는 것이 아니라 대행 함수를 통해 간접적으로 객체를 생성하는 방식을 Factory Method 패턴이라고 한다. Factory Method 패턴은 통상적으로 클래스 상속 구조와 같이 사용되는데 여기서 상위 클래스는 생성하는 객체의 종류와는 상관없이 공통적으로 사용할 있는 처리 모듈을 포함하게 된다. 이때 공통된 처리 모듈을 하위 클래스들이 공유하는 방식은 26장의 Template Method 패턴을 이용할 때가 많다.
  • 샘플 코드
#include <iostream>
#include <fstream>
#include <map>
using namespace std;
 
class Document
{
public:
virtual bool Open(char* pFileName) = 0;
};
 
class HwpDocument : public Document
{
public:
bool Open(char* pFileName)
{
ifstream ifs(pFileName);
if (!ifs)
return false;
 
// HWP 프로세싱 처리
return true;
}
};
 
class MsWordDocument : public Document
{
bool Open(char* pFileName)
{
ifstream ifs(pFileName);
if (!ifs)
return false;
 
// MsWord 프로세싱
return true;
}
};
 
class Application
{
public :
void NewDocument(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;
}
};
 
void main()
{
HwpApplication hwp;
hwp.NewDocument("input.hwp");
}
  • 구현 관련 사항
Factory Method 패턴의 구현 시에 추가로 고려해볼만한 사항들은 다음과 같다.
먼저 Factory Method 패턴의 구현 상속 구조 상에 놓인 최상위 클래스는 반드시 추상 클래스로 정의해야 하는지 생각해보자.
이를 위해 최상위 클래스를 추상 클래스로 정의하는 경우와 그렇지 않은 경우의 장단점을 살펴보면 우선 최상위 클래스를 추상 클래스로 정의할 경우에는 Factory Method 해당하는 멤버 함수를 반드시 하위 클래스에서 구현해야 하는 불편함이 있다. 반면 각각의 하위 클래스에 의해 생성되는 객체는 서로 명확히 구분된다.
한편 최상위 클래스를 추상 클래스로 정의하지 않을 경우에는 하위 클래스들이 별도로 Factory Method 해당하는 멤버 함수를 정의하지 않고, 상위 클래스에서 정의된 멤버 함수를 이용할 있는 장점이 있다. 반면 경우에는 특정 하위 클래스에서 생성하는 객체가 상위 클래스에서 생성하는 객체와 동일 수도 있기 때문에 이를 구분하는 신경을 필요가 있다.
이처럼 Factory Method 패턴을 구성하는 상속 구조 상에서 최상위 클래스를 추상 클래스로 정의할지 여부는 구체적인 상황에 따라 달라 있다. 따라서 Factory Method 패턴 구현 최상위 클래스를 반드시 추상 클래스로 정의할 필요는 없다.
Factory Method 패턴의 구현 고려해야 다른 문제로는 생성해야 객체의 종류가 늘어날 경우 계속해서 클래스를 증가시켜나갈 것인가 하는 것이다. 기본적인 Factory Method 패턴은 생성할 객체의 종류가 추가되면 이를 생성시켜 주기 위한 클래스를 따로 정의하는 식이다. 그러나 이처럼 생성해야 객체의 종류가 늘어날 때마다 이를 생성해주기 위한 클래스를 계속 추가한다면 클래스의 수가 너무 많아져 설계가 많이 복잡해질 것이다. 따라서 이를 방지하기 위해서는 생성할 객체의 종류와는 상관없이 동일한 클래스에서 객체를 생성해주는 형태로 변경이 필요하며, 이때 생성을 담당하는 멤버 함수는 어떤 객체를 생성해줄지에 대해 인자로 전달받는 식이어야 것이다.
  • FACTORY METHOD 패턴 정리
Factory Method 어떤 객체를 생성하는 역할을 담당하는 함수를 가리킨다. 일반적으로 이런 Factory Method 그림에서 보는 바와 같이 클래스 상속 관계와 함께 사용되며, 때의 클래스 구조를 Factory Method 패턴이라고 한다.

Factory Method패턴이 유용한 경우
  • 구체적으로 어떤 클래스의 객체를 생성해야 할지 미리 알지 못할 경우 Factory Method 패턴이 유용하다. 왜냐하면 Factory Method 패턴을 적용하면 구체적으로 생성될 객체의 자료형이 각각의 하위 클래스에 의해 결정되며, 이러한 하위 클래스는 필요한 경우 계속 확장 가능하기 때문이다.
  • 하위 클래스가 객체를 생성하기를 원할 Factory Method 패턴이 유용하다. 앞서 제시한 문제를 보면 Application 클래스는 문서 파일 객체의 생성을 하위 클래스인 HwpApplication 이나 MsWordApplication 클래스가 수행하도록 하고 있다. 따라서 Factory Method 패턴은 객체의 생성을 하위 클래스에게 위임하는 것이라고 있다.
하위 클래스들에게 개별 객체의 생성 책임을 분산시켜 객체의 종류 별로 객체 생성과 관련된 부분을 국지화시킬 유용하다.
Factory Method 패턴의 장점
  • 어떤 객체를 생성할 것인지와는 무관하게 동일한 형태로 프로그래밍이 가능하다는 점이다. 이는 개의 상위 클래스가 존재하기 때문에 가능한 것이다. , Create클래스와 Product 클래스가 각각 존재하기 때문에 어떤 객체를 생성하든지 상관없이 클래스 개가 제공하는 인터페이스를 이용해서 생성할 객체의 자료형과 무관하게 독립된 형태로 프로그래밍 있는 것이다.
  • 직접 생성자를 호출해서 객체를 생성하는 것보다 훨씬 유연하고 확장성 있는 구조라는 점이다. 왜냐하면 새로운 객체를 생성하거나 이전 객체를 확장해서 생성하고자 굳이 기존소스를 분석해서 변경할 필요없이 새로운 하위 클래스를 정의하고 Factory Method 해당하는 멤버 함수만 Override 시키면 충분하기 때문이다.
  • Factory Method 패턴에서 Factory Method 멤버 함수는 Create 클래스에 의해서만 불리워질 있는 아니라 외부에서도 얼마든지 불러서 사용할 있다. Factory Method 패턴은 Abstract Factory 패턴에서처럼 서로 연관된 클래스들의 상속 관계가 병렬로 존재해서 상속 관계의 클래스들이 다른 상속 관계의 클래스 객체들을 생성할 편리하다.
  • 상속 관계에 있는 클래스들의 멤버 함수가 동일한 프로그램 로직을 가지고 있으면서 내부적으로 생성할 객체만 서로 다를 Factory Method 패턴을 적용하면 편리하다. 왜냐하면 클래스마다 동일한 프로그램 로직의 멤버 함수를 각각 구현하지 않고, 상위 클래스에서 번만 구현한 다음 각각의 클래스는 객체를 생성해주는 멤버 함수만 다시 구현해주면 되기 때문이다.
Factory Method 패턴의 단점
  • 생성할 객체의 종류가 달라질 때마다 새로운 하위 클래스를 정의해야 한다는 점이다. 이는 불필요하게 많은 클래스를 정의하게 만들 있는 문제를 내포하고 있다.
반응형