Comments 25
Классно конечно, но по мне так гибче сделать у Creator'а сделать виртуальный метод canCreate, тогда можно будет на основе чего угодно выбирать нужную фабрику. Очень помогает при разборе xml'ек.
Спасибо за статью. Хорошее и простое описание.
Странно, но по-моему, предложенный Вами вариант больше смахивает на
тяжеловестного уродливого монста, чем switch/if-else-if конструкции. Да и понятней они, что-ли.
тяжеловестного уродливого монста, чем switch/if-else-if конструкции. Да и понятней они, что-ли.
Хм, а вот я бы по-другому сказал: в варианте автора статьи просто акцент не на фабриках как таковых, а о реализации их одним конкретным способом на конкретном языке. Собственно, Александреску до своего участия в разработке языка D и его стандартной библиотеки занимался как раз тем, что описывал решение общих проблем в контексте C++ — вспомните мультиметоды, которые есть в CLOS, но которые изначально отсутствуют в C++ и описаны им. Я знаю, многие могут скептически к этому отнестись, но ООП существует и в C, например, ибо ООП есть подход, но не реализация. И в C фабрики смотрятся даже естественнее, чем в C++, если вы хорошо знаете C и понимаете, о чём я. switch-case это неплохой подход для случая, когда вся логика лежит в одном месте. А если нет, то логичнее будет некий интерфейс, позволяющий на ходу добавлять творцов в пантеон, так сказать. Он может быть выражен классами и шаблонами в C++, а может и структурами и функциями в C. Ну и, честно говоря, результат автора мне тоже кажется несколько громоздким, но идея ведь в том, чтобы сделать производство объектов гибче, чем подход с switch-case.
Пожалуй, я малость увлёкся и ответил, в том числе, и на собственные мысли, ну да ладно.
Пожалуй, я малость увлёкся и ответил, в том числе, и на собственные мысли, ну да ладно.
В Вашем примере абстрактная фабрика просто не нужна. Она нужна в том случае, когда вы заранее не знаете, какие объекты будете создавать. Например, у меня абстрактная фабрика использовалась в связке с Property.
Вот пример, абстрактный, сферический в вакууме. Танки, конечно не имеют никакого отношения к реальности, поэтому к архитектуре танков просьба не придираться.
Обратите внимания, что пушка и двигатель у танка — это на самом деле смарт поинтеры, а свойства работают с ними так, как будто это строки. Это работает за счет того, что каждой такой строке сопоставлен объект в ObjectFactory.
Вот пример, абстрактный, сферический в вакууме. Танки, конечно не имеют никакого отношения к реальности, поэтому к архитектуре танков просьба не придираться.
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» );
Т.е. не смотря на то что типы разные мы можем работать с несколькими разными свойствами как с одним за счет того, что названия типов совпадают; аналогично с вложенными объектами, в рамках свойств реальный тип свойства не важен, главное чтобы был определен метод конверсии типа того чего мы хотим в тип свойства (и наоборот). Фабрика выполняет эту роль для преобразования объект<->строка (на самом деле не только фабрика, но это не относится к теме).
В коде выше (в его нормальном варианте) фабрика и свойства появились потому, что создаваемые объекты имели сложную структуру и создавать их руками стало невыносимо, через проперти намного легче.
Плюс это дало интересную плюшку:
Представим, что у нас есть некие характеристики, отличающиеся для горизонтали и вертикали.
CSomeParams, где Direction может быть CHorizontal или CVertical.
Так вот за счет неявного преобразования текста в тип фабрикой можно писать
.SetProperty( «Directed Params», «Something» );
что равносильно
.SetProperty( «Vertical Params», «Something» ).SetProperty( «Horizontal Params», «Something» );
Т.е. не смотря на то что типы разные мы можем работать с несколькими разными свойствами как с одним за счет того, что названия типов совпадают; аналогично с вложенными объектами, в рамках свойств реальный тип свойства не важен, главное чтобы был определен метод конверсии типа того чего мы хотим в тип свойства (и наоборот). Фабрика выполняет эту роль для преобразования объект<->строка (на самом деле не только фабрика, но это не относится к теме).
Вы дали ссылку на А.Александреску — Современное проектирование на С++,
но ни слова не сказали о том, что в этой книге есть целый раздел, посвященный порождающим шаблонам проектирования, где есть и реализация рассматриваемого Вами шаблона — Factory Method.
Глава 3. Порождающие паттерны.
Паттерн Factory Method, страница 111.
Также к этой книге прилагается библиотека Loki, в которой есть готовый и отлаженный код.
Чем ваша реализация отличается (лучше) от реализации Александреску?
Ваш код может быть проще, но есть и недостатки, например нет поддержки конструкторов с параметрами.
Хотелось бы увидеть сравнение этих реализаций.
Еще хотел бы обратить Ваше внимание на используемые термины:
Вы используете название класса AbstractFactory, абстрактная фабрика это совсем другой шаблон проектирования, это может сбивать с толку.
Еще Вы пишете: «Ну, а теперь создадим, на основе этого конкретного примера, вполне конкретный паттерн.»
Вы не создаете шаблон проектирования, Вы его реализуете.
но ни слова не сказали о том, что в этой книге есть целый раздел, посвященный порождающим шаблонам проектирования, где есть и реализация рассматриваемого Вами шаблона — Factory Method.
Глава 3. Порождающие паттерны.
Паттерн Factory Method, страница 111.
Также к этой книге прилагается библиотека Loki, в которой есть готовый и отлаженный код.
Чем ваша реализация отличается (лучше) от реализации Александреску?
Ваш код может быть проще, но есть и недостатки, например нет поддержки конструкторов с параметрами.
Хотелось бы увидеть сравнение этих реализаций.
Еще хотел бы обратить Ваше внимание на используемые термины:
Вы используете название класса AbstractFactory, абстрактная фабрика это совсем другой шаблон проектирования, это может сбивать с толку.
Еще Вы пишете: «Ну, а теперь создадим, на основе этого конкретного примера, вполне конкретный паттерн.»
Вы не создаете шаблон проектирования, Вы его реализуете.
По-вашему, читать Александреску вредно или бесполезно? Если программируешь на C++, лучше прочитать и то, и другое.
Как вы относитесь к boost в таком случае?
Так это не идеи вредные, а восприятие неправильное. Вы же не ратуете за запрет топоров только из-за того, что топором можно убить человека или отрубить себе руку?
Похоже, вы неправильно поняли смысл книги Александреску — это не учебник по C++ или алгоритмам, а описание реализации сложных архитектурных решений при помощи шаблонов для расширения кругозора опытных программистов. И да, я очень сильно сомневаюсь, что новичок вообще поймёт хотя бы 50% книги, ибо у меня на 100% понимание ушло около 5 чтений от и до, а читал я её, уже будучи достаточно закалённым в C++ (5 лет программирования, включая написание парсеров и небольшого компилятора).
Короче, если новичок в химии решил поиграть с нитроглицерином, прочитав книгу «Продвинутая технология синтеза нитроглицерина», а потом ему оторвало яйца — это вина не книги, а его самого. Особенно, если про опасность экспериментов явно сказано в предисловии.
К написанному добавлю, что часто возникает проблема как и когда регистрировать конкретные классы в фабрике.
Обычно ситуация такая: у нас есть базовый класс и мы добавляем по мере необходимости и/или разработки конкретные экземпляры. Хотелось бы избавиться от глобальной функции регистрации, где все объекты добавляются в одном месте, т.е., грубо говоря, так делать не желательно:
Нам придется все время следить и изменять этот код. Хотелось бы иметь возможность добавить .cpp и .h файл с новым классом и автоматически обеспечить регистрацию.
Одно из решений — использовать так называемый счетчик Шварца. Приведу фрагмент кода, там фабрика немного по-другому реализована, но думаю, что все должно быть понятно.
Базовый класс, который будет использоваться, например, для чтения XML элеменов:
Файл XmlPic.h:
файл XmlText.h
Обычно ситуация такая: у нас есть базовый класс и мы добавляем по мере необходимости и/или разработки конкретные экземпляры. Хотелось бы избавиться от глобальной функции регистрации, где все объекты добавляются в одном месте, т.е., грубо говоря, так делать не желательно:
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.
Ставим объекты на поток, паттерн фабрика объектов