Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Аргументированная фабрика

Anna | 24.06.2014 | нет комментариев

В нынешнем плане столкнулся с необходимостью генерировать объекты-преемники от всеобщего интерфейса, при этом порождать преемников нужно, передавая им в конструкторе некоторый объект-инициализатор. Конструктора по-умолчанию у классов может и не быть. При этом определенная фабрика объектов определяется в динамически подгружаемых плагинах, следственно должна иметь определенный интерфейс.

Могу предложить два метода решения данной задачи.

Начальные данные

Возможен, интерфейс фабрик должен выглядеть дальнейшим образом

class IFactoryBasic
{
public:
    IFactoryBasic() {}
    virtual ~IFactoryBasic() {}
    virtual Test* create(const QString &key, const QString &args)=0;
};

где Test — некоторый базовый класс

class Test
{
protected:
    QString _word;
public:
    Test(const QString &word):_word(word) {}
    virtual ~Test() {}
    virtual void test(){ qDebug()<<"test "<<_word; }
};

class TestChild: public Test
{
public:
    TestChild(const QString &word): Test(word) {}
    virtual void test() { qDebug()<<"test child"<<_word; }
};

TestChild — преемник Test
Оба класса принимают в конструкторе строковый параметр word, тот, что потом мы можем верифицировать в функции test().

1-й метод

Метод примитивный. Он основан на создании шаблонного каркаса для грядущей фабрики.

template<class T, class C, class A>
class IFactory
{
    QMap<QString, C* (T::*)(const A&) > handler;
protected:
    void add(const QString &key, C *(T::*func)(const A &))
    {
        handler[key]=func;
    }

public:
    IFactory() {}
    virtual ~IFactory() {}
    C *make(const QString &key, const A &args)
    {
        if(handler.contains(key))
        {
            T* inheritor = dynamic_cast<T*>(this);
            if(inheritor)
                return (inheritor->*handler[key])(args);
        }
        return 0;
    }
};

Тут есть маленькое обязательство для пользователей класса. 1-й шаблонный параметр должен быть классом, тот, что наследуется от IFactory. Дальше будут пояснения, для чего это было необходимо.
handler в классе IFactory — ассоциативный контейнер, содержащий ключ и соответствующую функцию создания объекта. Сигнатура функции порождения описывается как C* (T::*)(const A&), то есть возвращаемое значение будет иметь указатель на некоторый класс C, как довод функции передается ссылка на объект типа A.
Функция add(...) добавляет в контейнер пару ключ-функция <key,func>.
Функция make(...) вызывает функцию порождения, если она имеется в контейнере (заранее динамически преобразовав тип указателя this к типу преемника, напротив невозможно вызвать функции, которые там были определены).
Это стержневой каркас фабрики, осталось описать определенную фабрику

class FactoryFst: public IFactory<FactoryFst, Test, QString>, public IFactoryBasic
{
    Test *createOrigin(const QString &args){ return new Test(args); }
    Test *createChild(const QString &args) { return new TestChild(args); }
public:
    FactoryFst()
    {
        add("test", &FactoryFst::createOrigin);
        add("testchild", &FactoryFst::createChild);
    }

    Test *create(const QString &key, const QString &args) { return make(key, args); }
};

Несложно додуматься, что мы используем множественное наследование для удовлетворения требованиям интерфейса IFactoryBasic. Для иного родителя мы очевидно указываем преемника FactoryFst, возвращаемый указатель будет указателем на объект класса Test, и в качестве довода передается ссылка на объект QString.
В соответствии с этим определением создаются функции, генерирующие объекты типа Test и TestChild:
Test *createOrigin(const QString &args){ return new Test(args); } — создает объект типа Test, передавая ему в конструктор довод QString.
Test *createChild(const QString &args) { return new TestChild(args); } — подобно создает объект типа TestChild.
Остается только в конструкторе FactoryFst зарегистрировать данные функции и определить функциюcreate(...) интерфейса IFactoryBasic.

2-й метод

Данный способ в большей степени использует образцы.
Для построения фабрики нам необходима маленькая подготовка. Для начала необходимо определить некоторые используемые фабрикой классы.

Для начала определим вспомогательный класс для хранения значений передаваемого довода. Для того, чтоб дозволено было беспрепятственно применять указатели на данный класс, сделаем его наследуемым от нешаблонного базового класса.

class Arguments
{
public:
    virtual ~Arguments() {}
};

template<class T>
class TArguments: public Arguments
{
public:
    T arg;
    TArguments(T _arg):arg(_arg) {}
};

Arguments — базовый класс, TArguments — шаблонный класс для хранения передаваемого объекта.

Так же нам потребуется класс-шаблонная обертка для вызова оператора new.

class Container
{
public:
    virtual ~Container() {}
    virtual void *make( Arguments* ) = 0;
};

нешаблонный Container служит той же цели, что и Arguments. Чтоб мы неизменно могли вызвать функцию порождения make(...) для всякого его шаблонного преемника. Функция make(...) должна возвращать указатель на сделанный объект.


template<class T, typename A>
class TContainer: public Container
{
public:
    void *make(Arguments* args=0)
    {
        TArguments<A>* a = dynamic_cast< TArguments<A>* >( args );
        if(!a) return 0;
        return new T(a->arg);
    }
};

класс TContainer теснее шаблонный, в качестве доводов образца ему передается тип возвращаемого указателя T и тип довода для конструктора A.
В функции make(...) в качестве довода передается указатель на Arguments, но мы-то понимаем, что на самом деле это должен быть указатель на TArguments и пробуем динамически преобразовать тип. Если все реформирования прошли удачно, то можем создавать объект ранее определенного типа.

В выводе, каркас фабрики будет выглядеть дальнейшим образом:

template<typename C=void, typename A=void>
class TemplateFactory
{
    QMap<QString, Container*> handler;

public:
    TemplateFactory() {}
    virtual ~TemplateFactory(){}

    template<class T>
    void add(const QString &name)
    {
        handler.insert(name, new TContainer<T, A>());
    }

    C *make(const QString &name, const A &arg)
    {
        if(handler.contains(name))
            return static_cast<C*>(handler.value(name)->make(new TArguments<A>(arg)));
        return 0;
    }
};

Тут доводы образца C — базовый класс, A — довод конструктора порождаемых объектов.
Функция add(...) регистрирует новые классы в списке, make(...) создает объект класса, передавая в функцию ключ для выбора типа и довод конструктора. Реформирование типов static_cast применяется для реформирования типа void* в необходимый нам C*.
Все готово, чтоб сделать определенную фабрику.


class FactorySnd: public TemplateFactory<Test, QString>, public IFactoryBasic
{
public:
    FactorySnd()
    {
        add<Test>("test");
        add<TestCild>("testchild");
    }

    Test* create(const QString &name, const QString &arg){ return make(name, arg); }
};

Вновь применяется множественное наследование, переопределяется функция create(...). В конструкторе происходит регистрация классов.

Итог

Обе фабрики работают в чем дозволено убедиться, исполнив дальнейший код

    IFactoryBasic* factory  = new FactorySnd();
    Test* test = factory->create("test", "A");
    test->test();
    test = factory->create("testchild", "B");
    test->test();
    delete factory;
    factory = new FactoryFst();
    Test *stest = factory->create("test", "C");
    stest->test();
    stest = factory->create("testchild", "D");
    stest->test();

В консоли получаем дальнейший выхлоп:

test  "A" 
test child "B" 
test  "C" 
test child "D"
Завершение

Одна и та же задача, безусловно же, может иметь не одно решение. Представленные решения не являются исключительными и непогрешимыми. 1-й метод дает огромную волю, от того что предоставляет право определять способы генерации объектов, чай в этом же способе дозволено связать данный объект сигналами (если он преемник QObject) либо зарегистрировать его в Наблюдателе. Дозволено так же как-то видоизменить передаваемый в порождающий способ довод перед передачей его в конструктор. Но плата за это — больше трудное сопровождение кода, добавление нового порождаемого объекта. 2-й способ менее требователен в этом отношении, но оставляет поменьше воли пользователю. То есть создание объекта происходит так, как это описано в базовом классе и никак напротив. Так же фабрики ориентированы на создание объектов наследуемых от одного класса и принимающих в качестве довода один объект. В этом объекте-инициализаторе обязаны быть инкапсулированы все нужные свойства для создания нового объекта. Это, безусловно, не универсально, (накладывает определенные ограничения на создаваемые объекты) но для решения прикладной задачи абсолютно подходит.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB