абстрактная фабрика (factory) объектов c++

Oct 31, 2010 16:04

читая различные статьи и книги - разобраться быстро в этом вопросе оказалось не так просто, потому - захотелось облегчить путь тем, кому нужно срочно и лаконично. давно не писал ничего подобного, потому пост открыт для критики. также есть расхождения с общепринятыми наименованиями с целью упрощения восприятия. большая часть кода основана на реализации абстрактной фабрики объектов из книги Андрея Александреску "Современное проектирование на С++" (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 - см. здесь

программирование

Previous post Next post
Up