читая различные статьи и книги - разобраться быстро в этом вопросе оказалось не так просто, потому - захотелось облегчить путь тем, кому нужно срочно и лаконично. давно не писал ничего подобного, потому пост открыт для критики. также есть расхождения с общепринятыми наименованиями с целью упрощения восприятия. большая часть кода основана на реализации абстрактной фабрики объектов из книги Андрея Александреску "Современное проектирование на С++" (c).
если кратко - фабрика объектов нужна в том случае, когда необходимо с наименьшими правками кода добавить новый динамический объект с конкретной реализацией абстрактных общих методов. это может быть "драйвер", back-end, др. (например - при реализации SQL драйвера или wrapper'а для SCM-систем).
как только у вас в коде возникает необходимость в конструкции типа:
shape* p;
switch(objType) {
case CIRCLE: {
p = new Circle;
break;
}
case TRIANGLE: {
p = new Triangle;
}
}
p->show();
, стоит задуматься о реализации фабрики объектов.
суть реализации сводится к тому, чтобы один класс (для него часто используют шаблон singleton) хранил в себе ассоциативный массив "имя_объекта=>адрес_объекта" и позволял осуществлять "регистрацию" объектов (добавление в ассоциативный массив), передачу адреса объекта по его названию в ассоциативном массиве, удаление объекта из ассоциативного массива и т.п.
этот класс можно сделать и без применения шаблонов, но с ним - добавляется большая универсальность и удобство.
в описываемой реализации в качестве ассоциативного массива используется std::map. в качестве указателя на функцию используются стандартные ссылки на функцию (можно использовать boost::shared_ptr)
описание шаблона фабрики:
#include
#include
//шаблон фабрики для обработки ошибок в случае, если указатель на функцию не найден
//на него особо можно не обращать пока внимания - просто переписать как есть
//ничего значительного он не делает
template
struct def_err_policy {
struct excpt : public std::exception {
const char* what() const throw() {
return "fabric called with unknown type";
}
};
static product* on_unknown_type(const idtype& id){ throw excpt(); }
};
шаблон фабрики:
template <
class product, //абстрактный класс для классов-реализаций
typename idtype, //тип значений для идентификации классов-реализаций
class creator = product* (*)(), //указатель на функцию, создающую объект класса-реализации
template
class factory_err_policy = def_err_policy //тот самый шаблон выше - при желании можно вызывать шаблон со своей реализацией
>
class vfactory {
private:
typedef std::map
assoc_map; //описание типа для ассоциативного массива
assoc_map assoc_; //ассоциативный массив
public:
vfactory() :assoc_() {} //конструктор с инициализацией пустого ассоциативного массива
~vfactory() { //деструктор, стирающий все данные из ассоциативного массива
assoc_.erase(assoc_.begin(), assoc_.end());
}
product* create(const idtype& id) { //метод, возвращающий по наименованию объекта класса-реализации ссылку на этот объект
typename assoc_map::const_iterator i = assoc_.find(id);
if(i != assoc_.end()) { return (i->second)(); }
return factory_err_policy::on_unknown_type(id); //тот самый шаблон :) можно упростить до "throw std::runtime_err()"
}
bool reg(const idtype& id,creator cr){ //регистрация объекта класса-реализации - или просто добавление в ассоц. массив
return assoc_.insert(typename assoc_map::value_type(id, cr)).second != 0;
}
bool unreg(const idtype& id){ //удаление объекта класса-реализации из ассоц.массива
return assoc_.erase(id) != 0;
}
};
описание абстрактного класса реализации:
class vobj{
public:
vobj(){};
virtual void show() = 0; //метод для тестирования - будет отображать из какого класса он вызывается
};
описание классов-реализаций (не забудьте для них добавить "#include "):
class obj_circle : public vobj
{
public:
virtual void show() {
std::cout << "this is obj_circle::show" << std::endl;
}
};
class obj_triangle : public vobj
{
public:
virtual void show() {
std::cout << "this is obj_triangle::show" << std::endl;
}
};
функции для создания объектов классов-реализаций и возврата указателей на них (class creator из шаблона фабрики):
inline vobj* create_circle(){
return new obj_circle;
}
inline vobj* create_triangle(){
return new obj_triangle;
}
теперь можно протестировать нашу фабрику объектов:
int main() {
vfactory vf;
vf.reg("obj_circle",create_circle);
vf.reg("obj_triangle",create_triangle);
vobj* obj1 = vf.create("obj_circle");
vobj* obj2 = vf.create("obj_triangle");
obj1->show();
obj2->show();
vf.unreg("obj_circle");
vf.unreg("obj_triangle");
return 0;
}
после компиляции и запуска должно отобразиться:
this is obj_circle::show
this is obj_triangle::show
успешного и легкого Вам кодинга! :)
UPD: почистил код, пока не стал делать функторы и в процессе кроссплатформинга с pimpl - см. здесь