추상 팩토리 패턴을 알아볼 시간인데요, 팩토리 메소드 패턴을 설명할 때 사용했던 피자 프랜차이즈 사업의 사례를 계속 이용해 보겠습니다.
사업이 나날이 번창하면서 사장은 이윤을 극대화시키기 위해 피자의 원재료를 본점에서 직접 공급하기로 결정했습니다. 그동안 피자의 원재료는 각 지점마다 따로 구입해서 사용했지만 원재료를 본점에서 공급하면 보다 안정적인 가격과 품질을 보장할 수 있을 것 입니다. 문제는 각 지점마다 지역 소비자의 기호에 맞는 피자를 만들어야 하기 때문에 같은 레드 소스라도 LA 지점 레드 소스와 뉴욕 지점 레드 소스가 다르다는 점 입니다. 어떻게 하면 이 문제를 해결할 수 있을까요?
먼저 제품군(Product Familiy)에 대한 개념부터 알아보겠습니다.
제품(Product)이 여러 개가 있고, 각 제품들은 또 다시 여러 종류로 나뉠 때 같은 종류의 제품들을 모아놓은 것을 제품군이라고 합니다.
피자를 예로 들면, 피자는 도우, 소스, 토핑, 치즈 같은 제품으로 이루어져 있고 LA 풍 피자와 뉴욕 풍 피자 같은 제품군으로 나뉘어집니다. 다시말하면 LA 풍 제품군은 LA 풍 도우, LA 풍 소스 등의 제품으로 이루어져있고, 뉴욕 풍 제품군은 뉴욕 풍 도우, 뉴욕 풍 소스 등의 제품으로 이루어져있겠죠.
그러면 각각의 제품을 추상화하고 제품의 생성 과정을 앞서 배웠던 팩토리 메소드 패턴을 이용해서 캡슐화 해봅시다.
(이 글에서 사용되는 코드는 모두 의사 코드 입니다.)
class Dough
{
public:
virtual ~Dough() = 0;
};
Dough::~Dough() {}
class Sauce
{
public:
virtual ~Sauce() = 0;
};
Sauce::~Sauce() {}
class Topping
{
public:
virtual ~Topping() = 0;
};
Topping::~Topping() {}
class Cheese
{
public:
virtual ~Cheese() = 0;
};
Cheese::~Cheese() {}
class ThinDough: public Dough {};
class ThickDough: public Dough {};
class TomatoSauce: public Sauce {};
class MarinaraSauce: public Sauce {};
class VeggieTopping: public Topping {};
class ClamsTopping: public Topping {};
class MozzarellaCheese: public Cheese {};
class ReggianoCheese: public Cheese {};
class PizzaIngredientFactory
{
public:
virtual Dough* CreateDough() = 0;
virtual Sauce* CreateSauce() = 0;
virtual Topping* CreateToppings() = 0;
virtual Cheese* CreateCheese() = 0;
};
class LaPizzaIngredientFactory: public PizzaIngredientFactory
{
public:
// LA 풍 피자(제품군)에 맞는 원재료(제품)를 생성 합니다.
virtual Dough* CreateDough() { return new ThinDough; }
virtual Sauce* CreateSauce() { return new TomatoSauce; }
virtual Topping* CreateToppings() { return new VeggieTopping; }
virtual Cheese* CreateCheese() { return new MozzarellaCheese; }
};
class NyPizzaIngredientFactory: public PizzaIngredientFactory
{
public:
// 뉴욕 풍 피자(제품군)에 맞는 원재료(제품)를 생성 합니다.
virtual Dough* CreateDough() { return new ThickDough; }
virtual Sauce* CreateSauce() { return new MarinaraSauce; }
virtual Topping* CreateToppings() { return new ClamsTopping; }
virtual Cheese* CreateCheese() { return new ReggianoCheese; }
};
class PizzaStore
{
public:
Pizza* Order(const std::string& pizzaName)
{
Pizza* pizza = CreatePizza(pizzaName);
pizza->Prepare();
pizza->Bake();
pizza->Cut();
pizza->Box();
return pizza;
}
PizzaStore(PizzaIngredientFactory* pizzaIngredientFactory)
: m_PizzaIngredientFactory(pizzaIngredientFactory) {}
protected:
PizzaIngredientFactory* m_PizzaIngredientFactory;
private:
virtual Pizza* CreatePizza(const std::string& pizzaName) = 0;
};
class LaPizzaStore: public PizzaStore
{
private:
Pizza* CreatePizza(const std::string& pizzaName)
{
Pizza* pizza = NULL;
if (pizzaName == "LA 치즈 피자")
{
pizza = new LaCheesePizza(m_PizzaIngredientFactory);
}
else if (pizzaName == "LA 페퍼로니 피자")
{
pizza = new LaPepperoniPizza(m_PizzaIngredientFactory);
}
else if (pizzaName == "LA 야채 피자")
{
pizza = new LaVeggiePizza(m_PizzaIngredientFactory);
}
return pizza;
}
};
자, 그럼 뉴욕 풍 치즈 피자가 어떻게 만들어지는 지 한 번 볼까요?
class NyCheesePizza: public Pizza
{
public:
virtual void Prepare()
{
Dough* dough = m_PizzaIngredientFactory->CreateDough();
Sauce* sauce = m_PizzaIngredientFactory->CreateSauce();
Topping* topping = m_PizzaIngredientFactory->CreateToppings();
Cheese* cheese = m_PizzaIngredientFactory->CreateCheese();
// 준비된 재료로 피자를 굽기 좋게 준비
// ...
// ...
std::cout << "NY 치즈 피자 준비" << std::endl;
}
virtual void Bake() { std::cout << "NY 치즈 피자 굽기" << std::endl; }
virtual void Cut() { std::cout << "NY 치즈 피자 자르기" << std::endl; }
virtual void Box() { std::cout << "NY 치즈 피자 포장" << std::endl; }
NyCheesePizza(PizzaIngredientFactory* pizzaIngredientFactory)
: Pizza(pizzaIngredientFactory) {}
};
int main()
{
NyPizzaIngredientFactory nyPizzaIngredientFactory;
NyPizzaStore nyPizzaStore(&nyPizzaIngredientFactory);
Pizza* pizza = nyPizzaStore.Order("NY 치즈 피자");
return 0;
}
객체의 생성 과정을 캡슐화해서 클라이언트(코드 사용자 혹은 접근 영역의 코드 그 자체)가 객체의 구성이나 표현 방식에 의존적이지 않도록 만들어 줍니다. 다시말하면 객체의 생성 방식이나 종류가 바뀌더라도 클라이언트는 그 것에 대해 신경 쓸 필요가 없다는 뜻이죠. '수정이 예상되는 부분의 국지화' 팩토리 메소드 편에서 말씀드렸었죠?
추상 팩토리 패턴이 가지는 또 하나의 장점은 바로 제품과 제품군을 다룰 때 굉장히 편리하다는 점 입니다.
일단 제품군들이 실수로 섞여서 사용될 일이 자연스럽게 방지 되고, 각 제품군 간의 교체도 쉽습니다.
제품과 제품군이 팩토리 메소드 패턴과 추상 팩토리 패턴을 구분 짓는 핵심어라고 생각하시면 되겠네요.
참고/인용: Head First Design Patterns, GoF 디자인 패턴! 이렇게 활용한다

