팩토리 메서드나 내부 팩토리 방식은 객체 한 종류를 생성하는 경우에 유용하다. 만약 여러 종류의 연관된 객체들을 생성해야 할 경우는 떻게 해야할까? 바로 추상 팩토리 방식을 사용하면 된다.
뜨거운 음료를 판매하는 카페가 있고, 일단 종류가 커피와 차가 있다고 가정한다.
class HotDrink {
public:
virtual void prepare(int volume) = 0;
};
class Tea : HotDrink {
public:
void prepare(int volume) override {
cout << volume << "양의 차 준비" << endl;
}
};
class Coffee : HotDrink {
public:
void prepare(int volume) override {
cout << volume << "양의 커피 준비" << endl;
}
};
make_drink라는 함수를 전역에 선언하고, 클라이언트가 사용할 수 있게 한다.
unique_ptr<HotDrink> make_drink(string type) {
unique_ptr<HotDrink> drink;
if (type == "Tea" || type == "tea") {
drink = make_unique<Tea>();
drink->prepare(200);
}
else {
drink = make_unique<Coffee>();
drink->prepare(200);
}
}
이렇게 하나의 함수로 묶으면 차를 만드는 메커니즘과 커피를 만드는 메커니즘이 동시에 묶이기에 SRP를 위반하게 된다. 따라서 클래스 별로 분리한다.
먼저 뜨거운 음료라는 추상 클래스를 만든다.
class HotDrinkFactory {
public:
virtual unique_ptr<HotDrink> make() const = 0;
};
make()라는 특정 인터페이스를 규정하지만 구현하지는 않았다. 구체적인 팩토리 클래스를 구현하면 다음과 같다.
class TeaFactory : public HotDrinkFactory {
unique_ptr<HotDrink> make() const override {
return make_unique<Tea>();
}
};
class CoffeeFactory : public HotDrinkFactory {
unique_ptr<HotDrink> make() const override {
return make_unique<Coffee>();
}
};
이제 좀 더 상위 수준에서 다른 종류의 음료를 만들 수 있도록(예를 들어 뜨거운 음료 뿐 아니라 차가운 음료까지 만들 수 있도록) DrinkFactory라는 것을 두어 다양한 팩터리들을 참조로 가질 수 있도록 한다.
class DrinkFactory {
private:
map<string, unique_ptr<HotDrinkFactory>> hot_factories;
public:
DrinkFactory() {
hot_factories["coffee"] = make_unique<CoffeeFactory>();
hot_factories["tea"] = make_unique<TeaFactory>();
}
unique_ptr<HotDrink> make_drink(const string& name) {
auto drink = hot_factories[name]->make();
drink->prepare(200);
return drink;
}
};
문자열("coffee", "tea" ..)을 각 팩토리에 연관시킨 map을 멤버로 가지게 하고, make_drink() 인터페이스를 통해 음료를 팩토리에서 만들게 하고 이를 반환할 수 있게 한다.
함수도 변수에 저장될 수 있다(함수 포인터). 팩토리 객체 포인터를 저장하는 방식이 아닌 함수 포인터를 저장하여 음료를 생성하는 절차 자체를 내장하게 할 수 있다.
class DrinkFactory_withFunc {
private:
map<string, function<unique_ptr<HotDrink>()>> factories;
public:
DrinkFactory_withFunc() {
// 차 생성 함수 포인터 저장
factories["tea"] = [] {
auto tea = make_unique<Tea>();
tea->prepare(200);
return tea;
};
// 커피 생성 함수 포인터 저장
factories["coffee"] = [] {
auto coffee = make_unique<Coffee>();
coffee->prepare(200);
return coffee;
};
}
};
이를 통해 기존 추상 팩토리 형태에서의 make_drink 인터페이스를 아래와 같이 생략할 수 있다.
class DrinkFactory_withFunc {
private:
map<string, function<unique_ptr<HotDrink>()>> factories;
public:
...
unique_ptr<HotDrink> make_drink(const string& name) {
return factories[name]();
}
...
};