Саморегистрация классов в фабрике
От: remark Россия http://www.1024cores.net/
Дата: 17.03.06 11:25
Оценка: 31 (8) +1
Решение для типовой задачи, когда есть интерфейс, реализации интерфейса, и реализации создаются фабрикой по некоторому значению (типу реализации). В решении обеспечивается саморегистрация классов реализаций интерфейса в фабрике, т.о. отпадает необходимость в switch'е в фабрике, и решение становиться максимально открытым для расширения.
Может кому будет полезно. Или кто выскажет конструктивные замечания.


// Некий интерфейс, реализации которого будем создавать с помощью фабрики
struct IHeader
{
    typedef boost::shared_ptr<IHeader> Ptr;
    virtual ~IHeader() = 0 {};
};

// Интерфейс метакласса реализации IHeader
struct IHeaderWrapper
{
    typedef boost::shared_ptr<IHeaderWrapper> Ptr;
    virtual ~IHeaderWrapper() = 0 {};

    virtual IHeader::Ptr create() = 0;
};

// Фабрика реализаций IHeader
class HeaderFactory
{
private:
    typedef std::map<int, IHeaderWrapper::Ptr> map;

    static map& wrappers()
    {
        static map wrappers_instance;
        return wrappers_instance;
    }

public:
    // Метод создания экземпляра
    static IHeader::Ptr create(int type)
    {
        map::iterator iter = wrappers().find(type);
        if (wrappers().end() != iter)
            return iter->second->create();
        else
            throw std::runtime_error("unknown header type");
    }

    // Метод добавления нового типа экземпляра
    static void add(int type, IHeaderWrapper::Ptr wrapper)
    {
        if (!wrappers().insert(std::make_pair(type, wrapper)).second)
            throw std::logic_error("Header type added twice");
    }
};

// Метакласс реализации IHeader
template<typename HeaderType>
struct HeaderWrapper : IHeaderWrapper
{
    virtual IHeader::Ptr create()
    {
        return IHeader::Ptr(new HeaderType);
    }
};

// Базовый класс для реализаций IHeader - обеспечивает саморегистрацию реализаций в фабрике
template<typename Derived>
struct Header : IHeader
{
private:
    struct Initializer
    {
        Initializer()
        {
            // Саморегистрация
            HeaderFactory::add(Derived::TYPE, IHeaderWrapper::Ptr(new HeaderWrapper<Derived>));
        }
    };

    static Initializer initializer;

protected:
    ~Header()
    {
        // Надо обязательно заюзать эту переменную
        (void)initializer;
    }
};

template<typename HeaderType> typename Header<HeaderType>::Initializer Header<HeaderType>::initializer;

// Реализация IHeader
struct Header1 : Header<Header1>
{
    static const int TYPE = 1;
    // Реализация
};

// Реализация IHeader
struct Header2 : Header<Header2>
{
    static const int TYPE = 2;
    // Реализация
};

// Реализация IHeader
struct Header3 : Header<Header3>
{
    static const int TYPE = 3;
    // Реализация
};


int main()
{
    IHeader::Ptr h1 = HeaderFactory::create(1);
    IHeader::Ptr h2 = HeaderFactory::create(2);
    IHeader::Ptr h3 = HeaderFactory::create(3);
    IHeader::Ptr h4 = HeaderFactory::create(4); // Тут будет исключение
}




В принципе тут можно уменьшить кол-во классов путём совмещения: например код из HeaderFactory можно поместить в IHeader. Тут так сказать всё разложено по полочкам.

Заполнение фабрики информацией о реализациях происходит во время инициализации глобальных объектов. Т.е. уже к main() она "константная".

Сразу предвижу замечания по поводу оверхеда. Возможно он есть тут. Конечно для тривиальных случаев делать так не стоит. Но для сложных, я считаю такое решение оправдано. Зато это создаёт некую инфраструктуру, с которой "пользовательские классы" становяться проще. Зато тут всё ортогонально и все классы и методы простые, короткие и понятные. А с "более простыми решения" рука об руку идут switch'и, дублирование и другие прелести.

Плюс эту инфрастуктуру можно нагрузить ещё всякими дополнительными бонусами.

1. Например, в фабрику можно поместить метод:

// Фабрика реализаций IHeader
class HeaderFactory
{
public:
    // Проверка на существование типа
    static bool exists(int type)
    {
        return wrappers().end() != wrappers().find(type)
    }
};


Например, загрузили из БД тип (число), сразу можно проверить, а правилное ли это число, есть ли у нас такой тип.

2. С каждой реализацией можно связать гораздо больше информации, чем просто тип

// Реализация IHeader
struct Header1 : Header<Header1>
{
    // Связываем с типом числовое значение
    static const int TYPE = 1;

    // Связываем с типом другой тип. Например, тип диалога, который может отобразить информацию о данной реализации
    typedef Header1Dlg DlgType;

    // Связываем с типом некий флаг
    static const bool IS_SIMPLE = true;

    // Связываем с типом строку, которую мы можем выводить пользователю
    static const char* GetName()
    {
        return "SOME_HEADER";
    }

    // Реализация
};


Соответственно надо расширить IHeaderWrapper для поддержки этих значений.

3. В фабрику можно добавить метод для получения всех типов:

// Фабрика реализаций IHeader
class HeaderFactory
{
public:
    // Получить список всех метаклассов
    static std::vector<IHeaderWrapper::Ptr> getAllWrappers();
};



Имея всё это можно написать функцию, например, для заполнения комбобокса информацией о всех существующих типах + с каждым элементом комбобокса связать код этого типа.

Или функцию, которая выберет из всех типов типы, которые отвечают заданному критерию, например, у которых флаг IS_SIMPLE == true;

В общем можно много чего ещё полезного реализовать, причём реализовать так сказать "красиво".


Плюсы:
1. Практически нулевое дублирование, что важно для сопровождаемого кода
2. Инрастуктура функциональная, расширяемая и "умная"
3. Сами классы реализаций интерфейсов абсолютно самодостаточные, самоописываемые, т.е. высокая локальность данных и функций
4. Чтобы добавить новый класс реализации надо просто его описать и его поддержка автоматически появиться везде где она должна быть. Чем не может похвастаться традиционный подход — саму реализацию добавил, добавил её поддержку в фабрику, добавил её поддержку в интерфейс пользователя, а при загрузке из БД забыл.


... что-то уже до целой статьи дотягивает


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.