Это немного сложно. Вы пишете C++, поэтому вам не следует копировать Java. С++ здесь более выразителен.
- Вам не понадобятся явные методы
Create
/Free
— компилятор может сгенерировать их автоматически.
- Обязательно нужен виртуальный деструктор, иначе интерфейс будет бесполезен. Любой класс, от которого вы хотите создать производный класс, должен иметь виртуальный деструктор с очень немногими специализированными исключениями из этого правила.
- Все реализации виртуальных методов должны быть объявлены
override
, включая деструктор, но не virtual
, так как это нарушит DRY.
- Классы могут носить свои имена, так что фабрика может зарегистрировать их, просто зная их типы. Это необязательное поведение фабрики.
#include <QtCore>
class IAnimal {
public:
virtual int GetNumberOfLegs() const = 0;
virtual QString Speaks() = 0;
virtual ~IAnimal() {}
};
class Cat : public IAnimal {
public:
int GetNumberOfLegs() const override { return 4; }
QString Speaks() override { return QStringLiteral("Meow"); }
static auto className() { return "Cat"; }
};
class Dog : public IAnimal {
public:
int GetNumberOfLegs() const override { return 4; }
QString Speaks() override { return QStringLiteral("Woof"); }
static auto className() { return "Dog"; }
};
Теперь у нас может быть универсальная фабрика. Обратите внимание, что все разумные типы контейнеров C++ управляют своими данными. Вам не нужно явно очищать их при уничтожении. Мы используем C++11. Метод Register
будет принимать только типы, производные от Interface
, и этот метод автоматически создает функцию построения с использованием лямбда-выражения.
Время жизни экземпляра должно явно контролироваться путем его создания в main()
.
#include <type_traits>
#include <typeindex>
#include <map>
template <class Interface> class Factory {
template <class C, class T = void> struct enable_if_I :
std::enable_if<std::is_base_of<Interface, C>::value, T> {};
using create_fn = Interface* (*)();
std::map<QByteArray, create_fn, std::less<>> m_creators;
std::map<std::type_index, QByteArray> m_names;
static Factory *&instance_ref() { // assume no inline static yet
static Factory *m_instance;
return m_instance;
}
Factory(const Factory &) = delete;
Factory &operator=(const Factory &) = delete;
public:
Factory() {
Q_ASSERT(!instance());
instance_ref() = this;
}
virtual ~Factory() { instance_ref() = {}; }
Как правило, для регистрации требуются тип и имя производного класса. Это ничего не предполагает о том, есть ли в классе className()
членов. Фабрика хранит как фабричную функцию, так и имя. Это позволяет искать имя без использования className
в качестве виртуального метода интерфейса.
template <class T> typename enable_if_I<T>::type Register(const QByteArray &name) {
m_creators[name] = +[]()->Interface* { return new T(); };
m_names[{typeid(T)}] = name;
}
Когда имена классов известны, мы можем использовать их для регистрации одного или нескольких классов, учитывая только их типы.
template <class T1> typename enable_if_I<T1>::type Register() {
this->Register<T1>(T1::className());
}
template <class T1, class T2, class...T> typename enable_if_I<T1>::type Register() {
this->Register<T1>(T1::className());
this->Register<T2, T...>();
}
Методы создания экземпляра оптимизированы таким образом, чтобы не копировать заданное имя, независимо от формата. Вот почему мы используем карту std::map<K, V, std::less<>>
с прозрачным компаратором. QByteArray
предоставляет operator<
, который принимает различные типы в правой части, и чтобы использовать это, тип ключа (здесь: имя) должен попасть в компаратор.
template <typename T> static Interface *CreateA(T &&t) {
return instance() ? instance()->Create(std::forward<T>(t)) : nullptr;
}
Interface *Create(QLatin1String name) const { return Create(name.data()); }
template <typename T> Interface *Create(T &&name) const;
static const QByteArray &NameOfA(const Interface * obj);
const QByteArray &NameOf(const Interface *) const;
static Factory *instance() { return instance_ref(); }
};
template <class Interface>
template <typename T> Interface *Factory<Interface>::Create(T &&name) const {
auto it = m_creators.find(name);
return (it != m_creators.end()) ? it->second() : nullptr;
}
namespace detail {
const QByteArray & null() { static const QByteArray n; return n; }
}
template <class Interface>
const QByteArray &Factory<Interface>::NameOfA(const Interface *obj) {
return instance() ? instance()->NameOf(obj) : detail::null();
}
template <class Interface>
const QByteArray &Factory<Interface>::NameOf(const Interface *obj) const {
auto it = m_names.find(typeid(*obj));
return (it != m_names.end()) ? it->second : detail::null();
}
Универсальная фабрика берет интерфейс и конкретные типы и регистрирует их все в конструкторе. Это упрощает строительство заводов.
template <class Interface, class ...Types>
class GenericFactory : public Factory<Interface> {
public:
GenericFactory() {
this->template Register<Types...>();
}
};
using AnimalFactory = GenericFactory<IAnimal, Cat, Dog>;
Пример использования с утверждениями для указания желаемого поведения. Обратите внимание, что для уничтожения объектов достаточно delete
их экземпляра. Компилятор будет генерировать вызовы.
int main() {
Q_ASSERT(!AnimalFactory::instance());
{
AnimalFactory animals;
Q_ASSERT(AnimalFactory::instance());
auto *dog1 = AnimalFactory::CreateA("Dog");
Q_ASSERT(dynamic_cast<Dog*>(dog1));
Q_ASSERT(AnimalFactory::NameOfA(dog1) == Dog::className());
Q_ASSERT(dog1->Speaks() == QStringLiteral("Woof"));
auto *dog2 = AnimalFactory::CreateA(QLatin1String("Dog"));
Q_ASSERT(dynamic_cast<Dog*>(dog2));
auto *cat = AnimalFactory::CreateA("Cat");
Q_ASSERT(dynamic_cast<Cat*>(cat));
Q_ASSERT(cat->Speaks() == QStringLiteral("Meow"));
Q_ASSERT(AnimalFactory::NameOfA(cat) == Cat::className());
delete cat;
delete dog2;
delete dog1;
}
Q_ASSERT(!AnimalFactory::instance());
}
person
Kuba hasn't forgotten Monica
schedule
20.04.2018
return it.value()();
После того, как вы получили указатель на функцию с помощьюit.value()
, вам нужно вызвать функцию. - person Igor Tandetnik   schedule 19.04.2018