Создание конкретной фабрики


     Для начала, реализуем конкретную фабрику, не используя шаблоны, чтобы понять, как же она должна работать.

    И так, нам нужен класс, который сможет создавать объекты, поддерживающие единый интерфейс.

Назовем этот интерфейс Foo, а фабрику соответственно FooFactory. В качестве идентификатора для создания объекта возьмем строку.

сlass FooFactory

{ public:

 FooFactory();

~virtual FooFactory();

Foo * create(const std::string & id) const;

};

А как же мы будем добавлять классы и собственно создавать их экземпляры?

     В С++ есть шаблоны, которые и помогут решить эту не простую задачу.

Для создания объектов типа Foo мы будем использовать другие объекты, вспомогательные, которые возьмут на себя работу по созданию объектов нужного нам класса.

В простонародье такие классы называют creator.

Создается абстрактный класс creator, у которого обычно есть только один метод — создание объекта определенного интерфейса, в текущем варианте объекты типа Foo.

А на его основе уже создаются конкретные классы creator'ы.

Благодаря абстрактному классу creator, мы можем хранить набор конкретных creator'ов в любом контейнере.

В данном случае шаблоны позволяют генерировать код для конкретного creator'а на основе параметра шаблона.

Абстракный класс для создания объектов типа Foo:

 


Class abstractFooCreator

{

public:

    virtual fooCreator() {}

    virtual Foo * create() const = 0;

};

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

 

template <class C>

Class fooCreator: public abstractFooCreator

{

public:

    virtual Foo * create() const { return new C(); }

};

Теперь уже можно написать шаблонный метод для добавления класса в фабрику.

Единственное, что нам нужно уже выбрать контейнер для хранения наших creator'ов.

Выбором может быть std::map.

 

 

typedef std::map<std::string, abstractFooCreator*> FactoryMap;

FactoryMap _factory;

template <class C>

void add(const std::string & id)

{

    typename FactoryMap::iterator it = _factory.find(id);

    if (it == _factory.end())

              _factory[id] = new fooCreator<C>();

}

Появился первый рабочий вариант фабрики, в которую можно добавлять классы, поддерживающие интерфейс Foo.


     Создание объектов нужного типа, практически уже решена, так как по сути она зависела только от того каким образом мы будем хранить creator'ы.

 

 Foo * create(const std::string & id) const

{

    typename FactoryMap::iterator it = _factory.find(id);

    if (it!= _factory.end())

              return it->second->create();

    return 0;

}

Соберем весь код, чтобы увидеть полную картину.

 


Class abstractFooCreator

{

public:

virtual fooCreator() {}

    virtual Foo * create() const = 0;

};

template <class C>

Class fooCreator: public abstractFooCreator

{

public:

        

    virtual Foo * create() const { return new C(); }

};

Сlass FooFactory

{

protected:

    typedef std::map<std::string, abstractFooCreator*> FactoryMap;

    FactoryMap _factory;

public:

    FooFactory();

    ~virtual FooFactory();

    template <class C>

    void add(const std::string & id)

    {

              typename FactoryMap::iterator it = _factory.find(id);

              if (it == _factory.end())

                       _factory[id] = new fooCreator<C>();

    }

    Foo * create(const std::string & id)

    {

              typename FactoryMap::iterator it = _factory.find(id);

              if (it!= _factory.end())

                       return it->second->create();

              return NULL;

    }

};

Пример, как данную фабрику использовать. Добавление классов в фабрику:

FooFactory factory;

factory.add<MyFoo>("MyFoo");

factory.add<MyFoo2>("MyFoo2");

factory.add<ImprovedFoo>("ImprovedFoo");

Создание объекта с помощью фабрики:

Foo * p = factory.create("MyFoo2");

Вот так просто с помощью фабрики можно управлять созданием объектов, поддерживающих единый интерфейс.

 

Создание шаблонной фабрики

 

Ну, а теперь создадим, на основе этого конкретного примера, вполне конкретный паттерн.

 

Что нужно нашей фабрике, чтобы удовлетворять наши возможности? Конечно, это задание типа идентификатора, который можем быть как и enum'ом, так и std::string или любым другим типом подходящим для идентификатора.

Второе — это собственно тип объектов, которые мы будем создавать, роль которого в примере выше исполнял класс Foo.

 

template <class Base, class IdType>

Class ObjectFactory

{

public:

    ObjectFactory() {}

    virtual ~ObjectFactory();

    template <class C>

    void add(const IdType & id);

    Base * create() const;

};

 

Вот так будет выглядеть наша шаблонная фабрика.

Приступим к ее реализации.

Как и в примере с Foo, нам нужно решить проблему создания объектов конкретного типа.

То есть нам нужен абстрактный класс, у которого есть метод для создания объекта типа Base, и конкретный creator класс, который наследует этот абстрактный класс и реализует этот метод, используя шаблонный параметр.

 

template <class Base>

Class AbstractCreator

{

public:

    AbstractCreator() {}

    virtual ~AbstractCreator(){}

    virtual Base* create() const = 0;

};

template <class C, class Base>

class Creator: public AbstractCreator<Base>

{

public:

    Creator() { }

    virtual ~Creator() {}

    Base * create() const { return new C(); }

};

Ну, а теперь нужно лишь использовать эти классы в нашей фабрике.

 

template <class Base, class IdType>

Class ObjectFactory

{

protected:

    typedef AbstractCreator<Base> AbstractFactory;

    typedef std::map<IdType, AbstractFactory*> FactoryMap;

    FactoryMap    _factory;

public:

    ObjectFactory() {}

    virtual ~ObjectFactory();

    template <class C>

    void add(const IdType & id)

    {

              registerClass(id, new Creator<C, Base>());

    }

protected:

    void registerClass(const IdType & id, AbstractFactory * p)

    {

              typename FactoryMap::iterator it = _factory.find(id);

              if (it == _factory.end())

                       _factory[id] = p;

              else

                       delete p;

    }

};

 

Что существенного добавилось по сравнению с примером Foo?

Конечно же это метод registerClass, который в качестве параметра принимает объект типа AbstractFactory, который, в свою очередь, умеет создавать объекты того класса, который мы указываем как шаблонный параметр для метода add.

 

Добавляем возможность задания поведения фабрики

 

Чтобы уметь задавать поведение фабрики нам нужно использовать другой паттерн проектирования — Стратегия, так же известного под именем Политика.

Данный паттерн очень хорошо освещен в книге                

А. Александреску «Современное проектирование на С++».

 

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

Теперь встроим нашу политику в фабрику. Это можно сделать несколькими способами — просто наследоваться от класса политики, или использовать класс политики как атрибут фабрики.

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

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

Но все это очень трудно.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: