Я читал, что Pimpl хорош для двоичной совместимости, а интерфейсы хороши для того, чтобы легко переключать реализацию. Мне нужно объединить оба эти метода, чтобы мое приложение могло переключать базовую реализацию через файл конфигурации.
Вот как выглядит мой текущий дизайн:
класс Foo: предоставляет клиентский интерфейс API, здесь меня интересует совместимость с ABI
class IFoo: интерфейсный класс (все чисто виртуальные методы, виртуальный dtor)
Класс Vendor1Foo: реализует IFoo, используя библиотеку Vendor1
Класс Vendor2Foo: реализует IFoo, используя библиотеку Vendor2
Не используя pimpl и строго используя интерфейсы, клиентский код может выглядеть так:
IFoo* foo = new Vendor1Foo();
Проблема в том, что мой клиентский код вообще не может знать о Vendor1 или Vendor2, и Foo — лишь один из многих классов, для которых я должен сделать это.
Общая концепция того, что я пытаюсь сделать, заключается в следующем:
class foo
{
private:
QScopedPointer<IFoo> pimpl;
void initImpl(); // Reads from QSettings and initializes pimpl
}
Есть идеи для элегантного решения этой проблемы?
Я надеюсь придумать некоторые макросы или шаблонный класс / метод, чтобы помочь стандартизировать то, как я справляюсь с этим, и свести к минимуму нарушение DRY.
Шаблонный класс мог бы служить помощником pimpl, подобно тому, как Герб Саттер использовал обобщенную идиому pimpl для C ++ 11: herbsutter.com/gotw/_101 и он также должен содержать логику для создания правильной реализации в зависимости от конфигурации
Здесь присутствуют элементы идиомы pimpl, схемы моста и фабричной схемы. В моем примере выше initImpl () можно рассматривать как фабричный метод. Я ищу решение, которое может использовать или не использовать все эти шаблоны.
Я уже смотрела идиома c ++: реализация в зависимости от параметра шаблона а также большинство вопросов о прыщах на SO. Название выглядело многообещающе, но это не помогло в моем конкретном случае использования.
Я не могу использовать C ++ 11 и использую Qt. Д-указатели не решайте мою проблему, поскольку они связаны с одной реализацией.
То, что вы ищете, это шаблон проектирования моста
http://en.wikipedia.org/wiki/Bridge_pattern
Это может быть реализовано с использованием языка pimpl.
Заголовочный файл:
class IFoo {
public:
virtual void doA() = 0;
virtual void dob() = 0;
};
class Foo {
public:
Foo();
~Foo();
void doA() { impl->doA(); }
void doB() { impl->doB(); }
private:
IFoo* impl;
// if needed - add clone to IFoo...
Foo(const Foo&);
Foo& operator = (const Foo&);
};
Где-нибудь еще:
class Vendor1Foo : public IFoo { ... };
class Vendor2Foo : public IFoo { ... };
В файле .cpp:
Foo::Foo() : impl(createFooImpl()) {}
Чтобы сделать шаблон готовым к 40 классам:
template <class Interface>
Interface* createInterfaceFromConfig();
template <class Interface>
class ConcreteObject {
public:
ConcreteObject() : impl(createInterfaceFromConfig<Interface>())
Interface& interface() { return *impl; }
const Interface& interface() const { return *impl; }
private:
Interface* impl;
// if needed - add clone to IFoo...
ConcreteObject(const ConcreteObject&);
ConcreteObject& operator = (const ConcreteObject&);
};
// example
class IFoo { ... };
typedef ConcreteObject<IFoo> Foo;
// somewhere else do specialization (.cpp file)
template <>
IFoo* createInterfaceFromConfig<IFoo>() { ... }
и специализация для других 39 интерфейсов …
Я думаю, вы все усложняете. просто используйте завод из Foos.
//accessible from client code:
struct IFoo
{
virtual ~IFoo(){}
}
struct FooFactory
{
IFoo* createFoo() const;
// you may only need pimpl here to fix _factory_ _interface_.
// And you always have 1 factory
private:
FooFactroyPrivate* d;
}
//implementation:
IFoo* FooFactory::createFoo() const
{
//checking settings, creating implementation
}
Теперь, когда вы исправили интерфейс, вы можете добавлять новые реализации, поскольку ваши клиенты получают доступ только через интерфейс, вы можете изменять детали своей реализации.
То, что вы спрашиваете, мне кажется очень похожим на Внедрение зависимости. Некоторое время назад я искал DI-фреймворк для C ++ и нашел pococapsule. Я не использовал его в конце концов, поэтому я не могу его просмотреть, но посмотрите.
Это решение действительно сработало для меня, поэтому я помещаю его здесь в качестве ответа:
PimpleHelper.h является вспомогательным классом Pimpl для уменьшения кода котельной пластины. Использует VendorFactory для создания правильной реализации поставщика. Несколько поставщиков зарегистрируют тот факт, что они реализуют данный интерфейс; реализация только одного поставщика когда-либо была реализована для данного интерфейса.
#include <QSettings>
#include "VendorFactory.h"#include <cxxabi.h>
// Pimpl Helper
template<typename T>
class PimplHelper
{
public:
PimplHelper()
{
m_interfaceNameImplemented = demangle(typeid(T).name());
initializeImpl();
}
T* getImpl()
{
return theImpl.data();
}
private:
QScopedPointer< T > theImpl;
QString m_interfaceNameImplemented;
void initializeImpl()
{
// Read in configuration
QSettings settings("AppSettings.ini", QSettings::IniFormat);
QString vendorToUse = settings.value("VENDOR_IMPLEMENTATION_KEY", "Vendor1").toString();
qDebug() << "Vendor to use is: " << vendorToUse << " Interface Implemented: " << m_interfaceNameImplemented;// Obtain an instance of the vendor's class that implements the T interface
theImpl.reset(
VendorFactory<T>::create(vendorToUse, m_interfaceNameImplemented)
);
if(!theImpl)
qDebug() << "PimplHelper::initializeImpl, error resolving implementation for: "<< vendorToUse << " Interface Implemented: " << m_interfaceNameImplemented;
}
const QString demangle(const char* name)
{
int status = -4;
char* res = abi::__cxa_demangle(name, NULL, NULL, &status);
const char* const demangled_name = (status==0)?res:name;
QString ret_val(demangled_name);
free(res);
return ret_val;
}
};
VendorFactory.h создает экземпляры классов, реализованных различными поставщиками.
Поставщики регистрируют свои реализации на фабрике с помощью макроса.
#include <QtCore>
template< class T>
class VendorFactory
{
private:
typedef T* (*CreateFunc)();
typedef QMap<QString, CreateFunc> FunctionMap;
public:
static T * create(const QString& vendorName, const QString& interfaceName)
{
typename FunctionMap::iterator it = creators()->find(vendorName + interfaceName);
if (it == creators()->end())
return NULL;
return (it.value())();
}
static bool reg(const QString& vendorName, const QString& interfaceName, CreateFunc fun)
{
qDebug() << "Registering: " << vendorName + interfaceName << endl;
creators()->insert(vendorName + interfaceName, fun);
return true;
}
static FunctionMap * creators()
{
static FunctionMap* creators = new FunctionMap;
return creators;
}
virtual ~VendorFactory() {}
};/// @brief This registers a Vendor's class in the factory and adds a factory function named create_vendorImplClass()
/// and calls VendorFactory::reg() by the help of a dummy static variable to register the function.
/// @param vendorName A string representing the vendor's name
/// @param vendorImplClass The class implementing the interface given by the last parameter
/// @param interface The interface implemented by the vendorImplClass
#define REGISTER_IN_FACTORY( vendorName, vendorImplClass, interface ) \
namespace { \
interface* create_ ## vendorImplClass() { return new vendorImplClass; } \
static bool vendorImplClass ## _creator_registered = VendorFactory< interface >::reg( vendorName, # interface, create_ ## vendorImplClass); }
А вот как они используются:
Person.h (публичный API)
#include "IPerson.h"#include "PimplHelper.h"
// Public facing API
class Person: public IPerson
{
public:
Person()
{
impl.reset( new PimplHelper<IPerson>());
}
QString GetFirstName();
QString GetLastName();
private:
QScopedPointer< PimplHelper<IPerson> > impl;
};
Person.cpp (публичный API)
#include "Person.h"
QString Person::GetFirstName()
{ // I'd like to remove the call to getImpl() here
// and just use the overloaded -> operator, but it
// gives me a "has no member named GetFirstName()" error
return impl->getImpl()->GetFirstName();
}
QString Person::GetLastName()
{
return impl->getImpl()->GetLastName();
}
PersonImpl1.h содержит реализацию Vendor1
#include "IPerson.h"#include "VendorFactory.h"
// Private Implementation
class PersonImpl1: public IPerson
{
public:
PersonImpl1():
FirstName("Jon"), LastName("Skeet")
{}
QString GetFirstName()
{
return FirstName;
}
QString GetLastName()
{
return LastName;
}
private:
QString FirstName;
QString LastName;
};
REGISTER_IN_FACTORY("Vendor1", PersonImpl1, IPerson)
PersonImpl2.h содержит реализацию Vendor2
#include "IPerson.h"#include "VendorFactory.h"
// Private Implementation
class PersonImpl2: public IPerson
{
public:
PersonImpl2(): FirstName("Chuck"), LastName("Norris")
{}
QString GetFirstName()
{
return FirstName;
}
QString GetLastName()
{
return LastName;
}
private:
QString FirstName;
QString LastName;
};
REGISTER_IN_FACTORY("Vendor2", PersonImpl2, IPerson)
Наконец, main.cpp файл:
#include <QCoreApplication>
#include <QDebug>
#include "Person.h"
// The following needs to be included for the static/auto registration
// with the VendorFactory to occur. I'm not exactly sure why.
#include "PersonImpl1.h"#include "PersonImpl2.h"
int main(int argc, char *argv[])
{
Q_UNUSED(argc)
Q_UNUSED(argv)
Person* p = new Person();
qDebug() << "The person implemented is: "<< p->GetFirstName() << " " << p->GetLastName();
qDebug() << "exiting";
}
Вот список других вопросов SO, которые помогли мне до сих пор:
Создать экземпляр класса по имени?
Динамически регистрировать методы конструктора в AbstractFactory во время компиляции с использованием шаблонов C ++
Зарегистрировать создателя объекта в фабрике объектов
Entity / Component Systems в C ++. Как мне обнаружить типы и создать компоненты?
Есть ли способ создания объектов из строки, содержащей их имя класса?
Статическая переменная не инициализирована
Отмена результата std :: type_info :: name