Pull to refresh

Comments 25

Классно конечно, но по мне так гибче сделать у Creator'а сделать виртуальный метод canCreate, тогда можно будет на основе чего угодно выбирать нужную фабрику. Очень помогает при разборе xml'ек.
Спасибо за статью. Хорошее и простое описание.
Странно, но по-моему, предложенный Вами вариант больше смахивает на
тяжеловестного уродливого монста, чем switch/if-else-if конструкции. Да и понятней они, что-ли.
Хм, а вот я бы по-другому сказал: в варианте автора статьи просто акцент не на фабриках как таковых, а о реализации их одним конкретным способом на конкретном языке. Собственно, Александреску до своего участия в разработке языка D и его стандартной библиотеки занимался как раз тем, что описывал решение общих проблем в контексте C++ — вспомните мультиметоды, которые есть в CLOS, но которые изначально отсутствуют в C++ и описаны им. Я знаю, многие могут скептически к этому отнестись, но ООП существует и в C, например, ибо ООП есть подход, но не реализация. И в C фабрики смотрятся даже естественнее, чем в C++, если вы хорошо знаете C и понимаете, о чём я. switch-case это неплохой подход для случая, когда вся логика лежит в одном месте. А если нет, то логичнее будет некий интерфейс, позволяющий на ходу добавлять творцов в пантеон, так сказать. Он может быть выражен классами и шаблонами в C++, а может и структурами и функциями в C. Ну и, честно говоря, результат автора мне тоже кажется несколько громоздким, но идея ведь в том, чтобы сделать производство объектов гибче, чем подход с switch-case.
Пожалуй, я малость увлёкся и ответил, в том числе, и на собственные мысли, ну да ладно.
согласен с dslf
разве:
IAbstractFile create(string filename)
{
    string ext = get_extention(filename);
    if( ext == "xml") return new XML(filename);
    if( ext == "txt") return new TXT(filename);
    return null;
}

сложнее?
В Вашем примере абстрактная фабрика просто не нужна. Она нужна в том случае, когда вы заранее не знаете, какие объекты будете создавать. Например, у меня абстрактная фабрика использовалась в связке с Property.

Вот пример, абстрактный, сферический в вакууме. Танки, конечно не имеют никакого отношения к реальности, поэтому к архитектуре танков просьба не придираться.
class CTank
    : public ITank
{
    DECLARE_META_CLASS();
    std::shared_ptr< IEngine > m_pEngine;
    std::shared_ptr< IGun > m_pGun;
};
 
// Let 'Gun' is main Tank property.
// By appending this line we allow to create tanks based on the gun types,
// e.g. if we have 'laser' default gun type we will be able to create 'laser' tank
RUN_BEFORE_MAIN( Impl::EnuqueueObjectTypesRegistration< CTank >( "Gun" ) );
 
IMPLEMENT_META_CLASS( CTank, "Tank" )
{
    using namespace Properties;
 
    REGISTER_PROPERTY( MakeProperty( &CTank::m_pEngine"Engine" ).release() );
    REGISTER_PROPERTY( MakeProperty( &CTank::m_pGun"Gun" ).release() );
}
 
void CreateSomeTanks()
{
    std::unique_ptr< ITank > p1 = ObjectFactory().CreateObject< ITank >( "{Laser}" );
    auto a1 = MakeTransaction().MakeTrampoline( p1 );
    assert( a1.GetProperty( "Engine" ) == "{Default}" );
    assert( a1.GetProperty( "Gun" ) == "{Laser}" );
 
    std::unique_ptr< ITank > p2 = ObjectFactory().CreateObject< ITank >( "{Nuclear}" );
    auto a2 = MakeTransaction().MakeTrampoline( p2 );
    assert( a2.GetProperty( "Engine" ) == "{Default}" );
    assert( a2.GetProperty( "Gun" ) == "{Nuclear}" );
    a2.SetProperty( "Gun\Ammo""Uranium 235" );
}


Обратите внимания, что пушка и двигатель у танка — это на самом деле смарт поинтеры, а свойства работают с ними так, как будто это строки. Это работает за счет того, что каждой такой строке сопоставлен объект в ObjectFactory.
Согласен. Хороший пример где нужна фабрика — в unreal engine, для создания объектов из скриптов. Правда регистрация объектов и property сделана костылями(а по-другому в С++ никак) но все же работает.
Вообще для скриптов лучше какой-нибудь LUA прикрутить.

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

Плюс это дало интересную плюшку:

Представим, что у нас есть некие характеристики, отличающиеся для горизонтали и вертикали.
CSomeParams, где Direction может быть CHorizontal или CVertical.

Так вот за счет неявного преобразования текста в тип фабрикой можно писать
.SetProperty( «Directed Params», «Something» );
что равносильно
.SetProperty( «Vertical Params», «Something» ).SetProperty( «Horizontal Params», «Something» );

Т.е. не смотря на то что типы разные мы можем работать с несколькими разными свойствами как с одним за счет того, что названия типов совпадают; аналогично с вложенными объектами, в рамках свойств реальный тип свойства не важен, главное чтобы был определен метод конверсии типа того чего мы хотим в тип свойства (и наоборот). Фабрика выполняет эту роль для преобразования объект<->строка (на самом деле не только фабрика, но это не относится к теме).
Вы дали ссылку на А.Александреску — Современное проектирование на С++,
но ни слова не сказали о том, что в этой книге есть целый раздел, посвященный порождающим шаблонам проектирования, где есть и реализация рассматриваемого Вами шаблона — Factory Method.
Глава 3. Порождающие паттерны.
Паттерн Factory Method, страница 111.
Также к этой книге прилагается библиотека Loki, в которой есть готовый и отлаженный код.

Чем ваша реализация отличается (лучше) от реализации Александреску?
Ваш код может быть проще, но есть и недостатки, например нет поддержки конструкторов с параметрами.
Хотелось бы увидеть сравнение этих реализаций.

Еще хотел бы обратить Ваше внимание на используемые термины:
Вы используете название класса AbstractFactory, абстрактная фабрика это совсем другой шаблон проектирования, это может сбивать с толку.
Еще Вы пишете: «Ну, а теперь создадим, на основе этого конкретного примера, вполне конкретный паттерн.»
Вы не создаете шаблон проектирования, Вы его реализуете.
UFO just landed and posted this here
По-вашему, читать Александреску вредно или бесполезно? Если программируешь на C++, лучше прочитать и то, и другое.
UFO just landed and posted this here
Как вы относитесь к boost в таком случае?
UFO just landed and posted this here
В таком случае все-таки стоит согласиться с тем, что знать и читать надо. Не надо использовать где попало просто ради использования.
UFO just landed and posted this here
Так это не идеи вредные, а восприятие неправильное. Вы же не ратуете за запрет топоров только из-за того, что топором можно убить человека или отрубить себе руку?
UFO just landed and posted this here
Похоже, вы неправильно поняли смысл книги Александреску — это не учебник по C++ или алгоритмам, а описание реализации сложных архитектурных решений при помощи шаблонов для расширения кругозора опытных программистов. И да, я очень сильно сомневаюсь, что новичок вообще поймёт хотя бы 50% книги, ибо у меня на 100% понимание ушло около 5 чтений от и до, а читал я её, уже будучи достаточно закалённым в C++ (5 лет программирования, включая написание парсеров и небольшого компилятора).
UFO just landed and posted this here
Короче, если новичок в химии решил поиграть с нитроглицерином, прочитав книгу «Продвинутая технология синтеза нитроглицерина», а потом ему оторвало яйца — это вина не книги, а его самого. Особенно, если про опасность экспериментов явно сказано в предисловии.
UFO just landed and posted this here
UFO just landed and posted this here
К написанному добавлю, что часто возникает проблема как и когда регистрировать конкретные классы в фабрике.
Обычно ситуация такая: у нас есть базовый класс и мы добавляем по мере необходимости и/или разработки конкретные экземпляры. Хотелось бы избавиться от глобальной функции регистрации, где все объекты добавляются в одном месте, т.е., грубо говоря, так делать не желательно:

int main()
{	
    TypeFactory factory;
    factory.add<Foo>(fooType);
    factory.add<Bar>(barType);
}


Нам придется все время следить и изменять этот код. Хотелось бы иметь возможность добавить .cpp и .h файл с новым классом и автоматически обеспечить регистрацию.
Одно из решений — использовать так называемый счетчик Шварца. Приведу фрагмент кода, там фабрика немного по-другому реализована, но думаю, что все должно быть понятно.

#include <map>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>


template <class IdType, class Base>
class ObjectFactory : boost::noncopyable
{
public:
    typedef IdType IdTypeUsing;
protected:
    typedef boost::shared_ptr<Base> BasePtr;
    typedef boost::function<BasePtr()> CreateFunc;
    typedef std::map<IdType, CreateFunc> FactoryMap;

public:
    BasePtr create(const IdType & id) const
    {
        typename FactoryMap::const_iterator it = map_.find(id);
        return (it != map_.end()) ? (it->second)() : BasePtr();
    }

    void add(const IdType & id, CreateFunc func)
    {
        FactoryMap::iterator i = map_.find( id );
        if ( i == map_.end() )
            map_.insert(  FactoryMap::value_type(id, func) );        
        else
            i->second = func; // элемент уже есть: что то сделать, например, заменить
    }

private:
    FactoryMap map_;
};

template <class T>
class RegisterElement
{
public:
    typedef boost::shared_ptr<T> TPtr;
public:
    template <class Factory>
    RegisterElement(Factory & factory, const typename Factory::IdTypeUsing & id)
    {
        if (class_registered_++ == 0) // Jerry Schwarz counter
            factory.add(id, &CreateElmImpl);            
    };
private:
    static TPtr CreateElmImpl() { return TPtr( new T() ); }
    static int class_registered_;
};

template<class T> int RegisterElement<T>::class_registered_ = 0;


Базовый класс, который будет использоваться, например, для чтения XML элеменов:
class XmlElement {}; 
ObjectFactory<std::string, XmlElement> XmlFactory;


Файл XmlPic.h:
class Pic : public XmlElement {}; 

namespace  { 
// элемент зарегистрируется гарантированно один раз
RegisterElement<Pic> RegisterElementPic(XmlFactory, "pic");
}


файл XmlText.h
class Text : public XmlElement {};
namespace  {
RegisterElement<Text> RegisterElementText(XmlFactory, "text");
}

В тексте написано много слов, но не написано главного.

Естественно, "добавлять классы" в фабрику и "хранить список классов" в фабрике язык C++ не позволяет.

Вместо этого для каждого класса создается объект creator (причем все классы creator-ов созданы на основе одного родительского класса), и именно он добавляется в фабрику через метод add(). И при создании объекта нужного класса через метод create(), вызывается соответствующий creator.

Sign up to leave a comment.

Articles