팩토리 패턴 소개
팩토리 패턴은 생성형 디자인 패턴의 한 종류로, 객체 생성의 최상의 방법을 제공합니다. 팩토리 패턴에서는 클라이언트에게 객체 생성 로직을 노출하지 않고, 공통 인터페이스를 사용하여 새로 생성된 객체를 가리킵니다.
간단히 말해, C++의 다형성 특성을 활용하여 상속 관계에 있는 클래스들을 팩토리 클래스를 통해 해당 서브 클래스(파생 클래스) 객체를 생성합니다. 복잡한 프로젝트에서는 서브 클래스 객체 생성을 용이하게 만들 수 있습니다.
팩토리 패턴의 구현 방식은 단순 팩토리 패턴, 팩토리 메소드 패턴, 추상 팩토리 패턴으로 나뉘며, 각 구현 방식마다 장단점이 존재합니다.
최근 신발 거래가 뜨거워지면서, 신발 공장의 형태로 각 구현 방식을 분석해 보겠습니다.
단순 팩토리 패턴
구체적인 상황:
- 신발 공장은 나이키, 아디다스, 리닝 브랜드의 신발을 생산할 수 있습니다. 어떤 신발이 인기가 많으면 사장님이 그 신발을 생산하며, 상황에 따라 생산합니다.
단순 팩토리 패턴의 구성 요소:
- 팩토리 클래스(`SimpleFactory`): 팩토리 패턴의 핵심 클래스로, 지정된 구체적인 인스턴스 객체를 생성하기 위한 인터페이스를 정의합니다.
- 추상 제품 클래스(`Product`): 구체적인 제품 클래스가 상속받는 부모 클래스 또는 구현하는 인터페이스입니다.
- 구체적인 제품 클래스(`NikeProduct\AdidasProduct\LiNingProduct`): 팩토리 클래스가 생성하는 객체가 바로 이 구체적인 제품 인스턴스입니다.
단순 팩토리 패턴의 특징:
- 팩토리 클래스가 구체적인 제품 객체를 생성하는 함수를 캡슐화합니다.
단순 팩토리 패턴의 단점:
- 확장성이 매우 떨어집니다. 새로운 제품을 추가할 때 팩토리 클래스를 수정해야 합니다.
단순 팩토리 패턴 코드 예제:
// 제품 추상 클래스
class Product
{
public:
virtual ~Product() {}
virtual void Display() = 0;
};
// 나이키 제품
class NikeProduct : public Product
{
public:
void Display()
{
std::cout << "나는 나이키 제품입니다. 슬로건: Just do it" << std::endl;
}
};
// 아디다스 제품
class AdidasProduct : public Product
{
public:
void Display()
{
std::cout << "나는 아디다스 제품입니다. 슬로건: Impossible is nothing" << std::endl;
}
};
// 리닝 제품
class LiNingProduct : public Product
{
public:
void Display()
{
std::cout << "나는 리닝 제품입니다. 슬로건: Everything is possible" << std::endl;
}
};
enum ProductType
{
NIKE,
LINING,
ADIDAS
};
// 총 공장
class SimpleFactory
{
public:
// 제품 유형에 따라 해당 제품 객체 생성
Product *CreateProduct(ProductType type)
{
switch (type)
{
case NIKE:
return new NikeProduct();
case LINING:
return new LiNingProduct();
case ADIDAS:
return new AdidasProduct();
default:
return nullptr;
}
}
};
int main()
{
// 팩토리 객체 생성
SimpleFactory factory;
// 공장 객체로 나이키 제품 객체 생성
Product *pNike = factory.CreateProduct(NIKE);
if (pNike)
{
// 나이키 제품 광고 출력
pNike->Display();
// 리소스 해제
delete pNike;
}
// 공장 객체로 리닝 제품 객체 생성
Product *pLiNing = factory.CreateProduct(LINING);
if (pLiNing)
{
// 리닝 제품 광고 출력
pLiNing->Display();
// 리소스 해제
delete pLiNing;
}
// 공장 객체로 아디다스 제품 객체 생성
Product *pAdidas = factory.CreateProduct(ADIDAS);
if (pAdidas)
{
// 아디다스 제품 광고 출력
pAdidas->Display();
// 리소스 해제
delete pAdidas;
}
return 0;
}
팩토리 메소드 패턴
구체적인 상황:
- 각종 신발 거래가 매우 활발해지면서, 각 브랜드의 신발을 대량 생산하기 위해 각 브랜드별로 독립적인 생산 라인을 개설해야 합니다. 이때 각 생산 라인은 동일한 브랜드의 신발만 생산할 수 있습니다.
팩토리 메소드 패턴의 구성 요소:
- 추상 팩토리 클래스(`Creator`): 팩토리 메소드 패턴의 핵심 클래스로, 구체적인 제품을 생성하기 위한 인터페이스를 제공하며, 이는 구체적인 팩토리 클래스에서 구현됩니다.
- 구체적인 팩토리 클래스(`NikeCreator\AdidasCreator\LiNingCreator`): 추상 팩토리를 상속받아, 해당 구체적인 제품 객체를 생성하는 방식을 구현합니다.
- 추상 제품 클래스(`Product`): 구체적인 제품이 상속받는 부모 클래스(기본 클래스)입니다.
- 구체적인 제품 클래스(`NikeProduct\AdidasProduct\LiNingProduct`): 구체적인 팩토리가 생성하는 객체가 바로 이 클래스입니다.
팩토리 메소드 패턴의 특징:
- 팩토리 메소드 패턴은 팩토리 클래스를 추상화하여 구체적인 제품을 생성하는 인터페이스를 제공하고, 이를 서브 클래스에 위임합니다.
- 팩토리 메소드 패턴의 적용은 구체적인 제품 객체의 생성을 캡슐화하는 것뿐만 아니라, 구체적인 제품 객체의 생성을 구체적인 팩토리 클래스에 구현하는 것을 목표로 합니다.
팩토리 메소드 패턴의 단점:
- 새로운 제품이 추가될 때마다 해당 제품에 대한 구체적인 팩토리 클래스를 추가해야 합니다. 단순 팩토리 패턴에 비해 더 많은 클래스 정의가 필요합니다.
- 하나의 생산 라인은 하나의 제품만 생산할 수 있습니다.
팩토리 메소드 패턴 코드 예제:
// 총 공장
class Creator
{
public:
virtual Product *CreateProduct() = 0;
virtual ~Creator() {}
};
// 나이키 생산자/생산 라인
class NikeCreator : public Creator
{
public:
Product *CreateProduct() override
{
return new NikeProduct();
}
};
// 아디다스 생산자/생산 라인
class AdidasCreator : public Creator
{
public:
Product *CreateProduct() override
{
return new AdidasProduct();
}
};
// 리닝 생산자/생산 라인
class LiNingCreator : public Creator
{
public:
Product *CreateProduct() override
{
return new LiNingProduct();
}
};
int main()
{
// ================ 나이키 생산 프로세스 ==================== //
// 공장에 나이키 생산 라인 개설
Creator *nikeCreator = new NikeCreator();
// 나이키 생산 라인에서 제품 생산
Product *nikeProduct = nikeCreator->CreateProduct();
// 나이키 제품 광고 출력
nikeProduct->Display();
// 리소스 해제
delete nikeProduct;
delete nikeCreator;
// ================ 아디다스 생산 프로세스 ==================== //
// 공장에 아디다스 생산 라인 개설
Creator *adidasCreator = new AdidasCreator();
// 아디다스 생산 라인에서 제품 생산
Product *adidasProduct = adidasCreator->CreateProduct();
// 아디다스 제품 광고 출력
adidasProduct->Display();
// 리소스 해제
delete adidasProduct;
delete adidasCreator;
return 0;
}
추상 팩토리 패턴
구체적인 상황:
- 신발 공장은 사업을 확장하여 신발뿐만 아니라 운동 브랜드의 옷도 함께 생산하기로 결정했습니다.
추상 팩토리 패턴의 구성 요소:
- 추상 팩토리 클래스(`AbstractFactory`): 팩토리 메소드 패턴의 핵심 클래스로, 구체적인 제품을 생성하기 위한 인터페이스를 제공하며, 이는 구체적인 팩토리 클래스에서 구현됩니다.
- 구체적인 팩토리 클래스(`NikeFactory`): 추상 팩토리를 상속받아, 해당 구체적인 제품 객체를 생성하는 방식을 구현합니다.
- 추상 제품 클래스(`Footwear\Garment`): 구체적인 제품이 상속받는 부모 클래스(기본 클래스)입니다.
- 구체적인 제품 클래스(`NikeFootwear\NikeGarment`): 구체적인 팩토리가 생성하는 객체가 바로 이 클래스입니다.
추상 팩토리 패턴의 특징:
- 여러 제품군에서 제품 객체를 생성할 수 있는 인터페이스를 제공합니다. 예를 들어, 나이키 공장을 생성하면 나이키 신발 제품, 옷 제품, 바지 제품 등을 생성할 수 있습니다.
추상 팩토리 패턴의 단점:
- 팩토리 메소드 패턴과 마찬가지로, 새로운 제품이 추가될 때마다 해당 제품에 대한 구체적인 팩토리 클래스를 추가해야 합니다.
추상 팩토리 패턴 코드 예제:
// 기본 클래스: 의류
class Garment
{
public:
virtual void Display() = 0;
virtual ~Garment() {}
};
// 나이키 의류
class NikeGarment : public Garment
{
public:
void Display() override
{
std::cout << "나는 나이키 의류입니다. 패션에 최고예요!" << std::endl;
}
};
// 기본 클래스: 신발
class Footwear
{
public:
virtual void Display() = 0;
virtual ~Footwear() {}
};
// 나이키 신발
class NikeFootwear : public Footwear
{
public:
void Display() override
{
std::cout << "나는 나이키 신발입니다. 멋지게 해드릴게요!" << std::endl;
}
};
// 총 공장
class AbstractFactory
{
public:
virtual Footwear *CreateFootwear() = 0;
virtual Garment *CreateGarment() = 0;
virtual ~AbstractFactory() {}
};
// 나이키 생산자/생산 라인
class NikeFactory : public AbstractFactory
{
public:
Footwear *CreateFootwear() override
{
return new NikeFootwear();
}
Garment *CreateGarment() override
{
return new NikeGarment();
}
};
int main()
{
// ================ 나이키 생산 프로세스 ==================== //
// 공장에 나이키 생산 라인 개설
AbstractFactory *nikeFactory = new NikeFactory();
// 나이키 생산 라인에서 신발 생산
Footwear *nikeShoes = nikeFactory->CreateFootwear();
// 나이키 생산 라인에서 의류 생산
Garment *nikeClothes = nikeFactory->CreateGarment();
// 나이키 신발 광고 출력
nikeShoes->Display();
// 나이키 의류 광고 출력
nikeClothes->Display();
// 리소스 해제
delete nikeShoes;
delete nikeClothes;
delete nikeFactory;
return 0;
}
요약
위 세 가지 팩토리 패턴은 모두 새로운 제품을 추가할 때 일정한 단점을 가지고 있습니다.
- 단순 팩토리 패턴은 공장 클래스를 수정해야 하므로 개방-폐쇄 원칙을 위반합니다.
- 팩토리 메소드 패턴과 추상 팩토리 패턴은 새로운 제품을 추가할 때 해당 제품에 대한 구체적인 팩토리 클래스를 추가해야 하므로 코드 작성량이 증가합니다.
그렇다면 새로운 제품을 추가할 때 공장 클래스를 수정하지 않고, 구체적인 팩토리 클래스도 추가하지 않는 좋은 방법은 없을까요?
실제 프로젝트에서 확장성이 매우 뛰어난 팩토리 클래스를 보았으며, 새로운 제품을 확장할 때 공장 클래스를 수정하거나 구체적인 팩토리 클래스를 추가할 필요가 없었습니다. 자세한 내용은 'C++ 팩토리 패턴 심층 분석: 고급편'으로 이동하여 읽어보시기 바랍니다.