Синглтоны и фабрики с несколькими библиотеками в C ++ - PullRequest
2 голосов
/ 01 апреля 2012

Некоторое время я читал и искал в Интернете, но не нашел хорошего решения.Вот что я хочу сделать:

Я пишу библиотеку, которая определяет абстрактный базовый класс - давайте назовем его IFoo .

class IFoo {
    public:
        virtual void doSomething() = 0;
        virtual ~IFoo() {}
};

Библиотека также определяетпара реализаций для этого - давайте назовем их FooLibOne и FooLibTwo .

Для того, чтобы инкапсулировать процесс создания и решить, какая конкретная реализация используется в зависимости от некоторого параметра времени выполненияЯ использую фабрику FooFactory , которая отображает std :: string на методы фабрики (в моем случае boost :: function, но это не должно быть здесь).Это также позволяет регистрировать новые фабричные методы.Это выглядит примерно так:

class FooFactory {
    public:
         typedef boost::function<IFoo* ()> CreatorFunction;

         IFoo* create(std::string name);
         void registerCreator(std::string name, CreatorFunction f);

    private:
         std::map<std::string, CreatorFunction> mapping_;
};

На данный момент я добавил реализации, предоставленные (FooLibOne, FooLibTwo) библиотекой непосредственно в конструктор FooFactory - таким образом, они всегда доступны.В некотором коде библиотеки для инициализации определенных объектов используется FooFactory и т. Д. До сих пор я воздерживался от использования шаблона Singleton для фабрик, поскольку шаблон tee обсуждается достаточно часто, и я не был уверен, как будут работать различные реализации шаблона Singleton.в сочетании с, возможно, несколькими общими библиотеками и т. д.

Однако обход заводов может быть немного громоздким, и я все еще думаю, что это один из случаев, когда шаблон Singleton мог бы быть полезным.Особенно, если учесть, что пользователи библиотеки должны иметь возможность добавить больше реализаций IFoo , которые также должны быть доступны для (уже существующего) кода библиотеки.Конечно, Dependency Injection - то есть я передаю экземпляр фабрики через конструктор - может сделать свое дело (и делает это сейчас).Но этот подход не работает, если я хочу быть еще более гибким и ввести второй уровень создания динамических объектов.Значение: я хочу динамически создавать объекты (см. Выше) в динамически создаваемых объектах (скажем, реализации абстрактного базового класса IBar - BarOne и BarTwo - снова черезфабрика BarFactory ).

Допустим, BarOne требует IFoo объекта, но BarTwo этого не делает.Я все еще должен предоставить FooFactory BarFactory в любом случае, поскольку одна из IBar реализаций может нуждаться в этом.Наличие глобально доступных фабрик могло бы смягчить эту проблему, и я не был бы вынужден предвидеть, какие фабрики могут понадобиться при реализации определенного интерфейса.Кроме того, я мог бы зарегистрировать методы создания непосредственно в исходном файле реализаций.

FooFactory::Instance().registerCreator("new_creator", boost::bind(...));

Поскольку я думаю, что это хорошая идея, каким будет правильный способ ее реализации?Я собирался использовать шаблонный подход, такой как SingletonHolder из Modern C ++ Design (см. Также Loki library), чтобы обернуть фабрики.Однако вместо этого я предпочел бы реализовать его как синглтон Майера.Но я все еще думаю, что будут проблемы с общими библиотеками.Решение должно работать с GCC (и предпочтительно MSVC).Я также открыт для других идей с точки зрения дизайна, но, пожалуйста, избегайте распространенных грантов "Singletons are evil".; -)

Заранее спасибо.

1 Ответ

0 голосов
/ 02 апреля 2012

Надеемся, что 76 строк кода говорят больше, чем несколько слов - (используя версию C ++ 11 вместо улучшенных, но в любом случае они почти одинаковые)

Я бы поместил определение фабрики (ий) и определение создателей в одну (или близкую) область видимости, чтобы каждый из создателей мог "видеть" «любой из их зависимых фабрик - избегая необходимости слишком много обходить фабрик и избегая одиноких»

Автомобили и сирены:

class ISiren {};
class Siren : public ISiren 
{
public:
    Siren() { std::cout << "Siren Created" << std::endl; }
};

class ICar{};
class EstateCar : public ICar 
{
public:
    EstateCar() { std::cout << "EstateCar created" << std::endl;}
};
class PoliceCar : public ICar 
{
    std::shared_ptr<ISiren> siren;
public:
    PoliceCar( std::shared_ptr<ISiren> siren)
        : siren( siren ) 
    {
        std::cout << "PoliceCar created" << std::endl;
    }
};

Фабрика

typedef std::function< std::shared_ptr<ICar> () > CreatorType;

class CarFactory
{
    std::map<std::string, CreatorType> creators;
public:
    void AddFactory( std::string type, CreatorType func )
    {
        creators.insert( std::make_pair(type, func) );
    }

    std::shared_ptr<ICar> CreateCar( std::string type )
    {
        CreatorType& create( creators[type] );
        return create();
    }
};

class SirenFactory
{
public: // Simple factory creating 1 siren type just for brevity
    std::shared_ptr<ISiren> CreateSiren() { return std::make_shared<Siren>(); }
};

«Заводской корень» (основная функция, где бы ни определялись фабрики):

int main()
{
    CarFactory car_factory; // Car factory unaware of Siren factory
    SirenFactory siren_factory;

    auto EstateCarLambda = []() { 
        return std::make_shared<EstateCar>(); 
    }; // Estate car lambda knows nothing of the Siren Factory

    auto PoliceCarLambda = [&siren_factory]() {
        return std::make_shared<PoliceCar>( siren_factory.CreateSiren() );
    };  // Police car creation lambda using the Siren Factory

    car_factory.AddFactory( "EstateCar", EstateCarLambda );

    car_factory.AddFactory( "PoliceCar", PoliceCarLambda );

    std::shared_ptr<ICar> car1 = car_factory.CreateCar( "EstateCar" );
    std::shared_ptr<ICar> car2 = car_factory.CreateCar( "PoliceCar" );
}
...