diff options
author | Karsten Heimrich <karsten.heimrich@theqtcompany.com> | 2015-07-07 13:13:09 +0200 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@theqtcompany.com> | 2015-07-10 08:53:49 +0000 |
commit | 39a56f719a24a75e303d3a34f73a9c8cb1a01a34 (patch) | |
tree | b5e3c3c68ef0c23eb1a06dfe9c7586e3337ae80e | |
parent | d01de18c1edfc7613f5c521452b595e71cd6e94d (diff) |
Add factory auto test. Update template parameter + documentation.
The create function cannot be documented properly, cause qdoc
does not understand the ellipsis(...) operator as of right now.
Change-Id: I6569372ed37621eeaaa7a41ea49aa795676ca65b
Reviewed-by: Kai Koehne <kai.koehne@theqtcompany.com>
-rw-r--r-- | src/libs/kdtools/kdgenericfactory.cpp | 78 | ||||
-rw-r--r-- | src/libs/kdtools/kdgenericfactory.h | 29 | ||||
-rw-r--r-- | tests/auto/installer/factory/factory.pro | 5 | ||||
-rw-r--r-- | tests/auto/installer/factory/tst_factory.cpp | 228 | ||||
-rw-r--r-- | tests/auto/installer/installer.pro | 3 |
5 files changed, 310 insertions, 33 deletions
diff --git a/src/libs/kdtools/kdgenericfactory.cpp b/src/libs/kdtools/kdgenericfactory.cpp index 9dbfc101d..2a7718be3 100644 --- a/src/libs/kdtools/kdgenericfactory.cpp +++ b/src/libs/kdtools/kdgenericfactory.cpp @@ -35,45 +35,83 @@ #include "kdgenericfactory.h" /*! - \inmodule kdupdater - \class KDGenericFactory - \brief The KDGenericFactory class implements a template-based generic factory. - - KDGenericFactory is an implementation of the factory pattern. It can be used to produce - instances of different classes having a common superclass \c T_Product. The user of the - factory registers those producible classes in the factory by using an identifier - \c T_Identifier. That identifier can then be used to produce as many instances of the - registered product as the user wants. + \inmodule kdupdater + \class KDGenericFactory + \brief The KDGenericFactory class implements a template-based generic factory. + + KDGenericFactory is an implementation of the factory pattern. It can be used to produce + instances of different classes having a common superclass \c BASE. The user of the factory + registers those producible classes in the factory by using the identifier \c IDENTIFIER. That + identifier can then be used to produce as many instances of the registered product as the + user wants. + + One factory instance is able to produce instances of different types of DERIVED classes only + when the constructor of DERIVED or the registered generator function have a common signature + for all DERIVED classes. This signature is described by the declaration order of ARGUMENTS. It + is referred to as SIGNATURE in the following paragraphs. + + If a class derived from BASE does not contain a SIGNATURE matching the registered one for the + constructor or the generator function, it is not possible to create instances of it using one + instance of KDGenericFactory subclass. In that case, more than one KDGenericFactory subclass + and instance are needed. + + It is possible to register a subclass of BASE inside an instance of KDGenericFactory subclass + using the registerProduct() function. At least one of the following conditions needs to be met: + + \list + \li A global or static function with SIGNATURE exists. + \li The DERIVED class has a constructor with SIGNATURE. + \li The DERIVED class has a static function with SIGNATURE. + \endlist + + To get a new instance of DERIVED, one needs to call the create() function. The value of + IDENTIFIER determines the product's subclass registered in the factory, while the values + of SIGNATURE are the actual arguments passed to the class constructor or the registered + generator function. +*/ + +/*! + \fn KDGenericFactory::KDGenericFactory() + + Creates the generic factory. */ /*! - \fn KDGenericFactory::~KDGenericFactory() + \fn KDGenericFactory::~KDGenericFactory() - Destroys the generic factory. + Destroys the generic factory. */ /*! \typedef KDGenericFactory::FactoryFunction - This typedef defines a factory function producing an object of type T_Product. + This typedef defines a factory function producing an object of type BASE. +*/ + +/*! + \fn void KDGenericFactory::registerProduct(const IDENTIFIER &id) + + Registers a type DERIVED, identified by \a id in the factory. Any type with the same id gets + unregistered. */ /*! - \fn KDGenericFactory::registerProduct(const T_Identifier &id) + \overload + \fn void KDGenericFactory::registerProduct(const IDENTIFIER &id, FactoryFunction func) - Registers a product of the type T, identified by \a id in the factory. Any type with the same id - gets unregistered. + Registers a function \a func that can create the type DERIVED, identified by \a id in the + factory. Any type with the same id gets unregistered. */ /*! - \fn bool KDGenericFactory::containsProduct(const T_Identifier &id) const + \fn bool KDGenericFactory::containsProduct(const IDENTIFIER &id) const - Returns \c true if the factory contains a product with the \a id; otherwise returns false. + Returns \c true if the factory contains a type with the \a id; otherwise returns false. */ /*! - \fn KDGenericFactory::create(const T_Identifier &id) const + \fn BASE *KDGenericFactory::create(const IDENTIFIER &id, ARGUMENTS... args) const - Creates and returns a product of the type T identified by \a id. Ownership of the product is - transferred to the caller. + Creates and returns the type identified by \a id, but automatically upcasted to BASE. Ownership + of the type is transferred to the caller. */ diff --git a/src/libs/kdtools/kdgenericfactory.h b/src/libs/kdtools/kdgenericfactory.h index 719a4f9da..048dc90ad 100644 --- a/src/libs/kdtools/kdgenericfactory.h +++ b/src/libs/kdtools/kdgenericfactory.h @@ -39,48 +39,53 @@ #include <QtCore/QHash> -template <typename T_Product, typename T_Identifier = QString, typename... T_Argument> +template <typename BASE, typename IDENTIFIER = QString, typename... ARGUMENTS> class KDGenericFactory { public: virtual ~KDGenericFactory() {} - typedef T_Product *(*FactoryFunction)(T_Argument...); + typedef BASE *(*FactoryFunction)(ARGUMENTS...); - template <typename T> - void registerProduct(const T_Identifier &id) + template <typename DERIVED> + void registerProduct(const IDENTIFIER &id) { - m_hash.insert(id, &KDGenericFactory::create<T>); + m_hash.insert(id, &KDGenericFactory::create<DERIVED>); } - bool containsProduct(const T_Identifier &id) const + void registerProduct(const IDENTIFIER &id, FactoryFunction func) + { + m_hash.insert(id, func); + } + + bool containsProduct(const IDENTIFIER &id) const { return m_hash.contains(id); } - T_Product *create(const T_Identifier &id, T_Argument... args) const + BASE *create(const IDENTIFIER &id, ARGUMENTS... args) const { const auto it = m_hash.constFind(id); if (it == m_hash.constEnd()) return 0; - return (*it)(args...); + return (*it)(std::forward<ARGUMENTS>(args)...); } protected: KDGenericFactory() = default; private: - template <typename T> - static T_Product *create(T_Argument... args) + template <typename DERIVED> + static BASE *create(ARGUMENTS... args) { - return new T(args...); + return new DERIVED(std::forward<ARGUMENTS>(args)...); } KDGenericFactory(const KDGenericFactory &) = delete; KDGenericFactory &operator=(const KDGenericFactory &) = delete; private: - QHash<T_Identifier, FactoryFunction> m_hash; + QHash<IDENTIFIER, FactoryFunction> m_hash; }; #endif diff --git a/tests/auto/installer/factory/factory.pro b/tests/auto/installer/factory/factory.pro new file mode 100644 index 000000000..b87f23bab --- /dev/null +++ b/tests/auto/installer/factory/factory.pro @@ -0,0 +1,5 @@ +include(../../qttest.pri) + +QT -= gui + +SOURCES += tst_factory.cpp diff --git a/tests/auto/installer/factory/tst_factory.cpp b/tests/auto/installer/factory/tst_factory.cpp new file mode 100644 index 000000000..bd77f1877 --- /dev/null +++ b/tests/auto/installer/factory/tst_factory.cpp @@ -0,0 +1,228 @@ +/************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include <kdgenericfactory.h> + +#include <QDebug> +#include <QTest> + +#include <functional> + +class Food { +public: + explicit Food(int amount) + : m_amount(amount) + {} + virtual ~Food() {} + + int available() const { + return m_amount; + } + virtual QDate expireDate() const = 0; + + template <typename T, typename... Args> + static Food *create(Args... args) { + qDebug().nospace().noquote() << "Create function."; + return new T(std::forward<Args>(args)...); + } + +private: + int m_amount; +}; + +class EggStore : public KDGenericFactory<Food, QString, int> +{ +public: + static EggStore &instance() { + static EggStore factory; + return factory; + } +}; + +class Bread : public Food +{ +public: + Bread(int amount, const QDate &expireDate) + : Food(amount) + , m_expireDate(expireDate) + { qDebug().nospace().noquote() << "Constructor."; } + QDate expireDate() const { + return m_expireDate; + } +private: + QDate m_expireDate; +}; + +class Butter : public Food +{ +public: + QDate expireDate() const { + return m_expireDate; + } + static Food *create(int amount, const QDate expireDate) { + qDebug().nospace().noquote() << "Create function."; + return new Butter(amount, expireDate); + } + +private: + Butter(int amount, const QDate &expireDate) + : Food(amount) + , m_expireDate(expireDate) + { qDebug().nospace().noquote() << "Constructor."; } + +private: + QDate m_expireDate; +}; + +class ColdCuts : public Food +{ +public: + ColdCuts(int amount, const QDate &expireDate) + : Food(amount) + , m_expireDate(expireDate) + { qDebug().nospace().noquote() << "Constructor."; } + QDate expireDate() const { + return m_expireDate; + } + +private: + QDate m_expireDate; +}; + +class FoodStore : public KDGenericFactory<Food, QString, int, QDate> +{ +public: + static FoodStore &instance() { + static FoodStore factory; + return factory; + } +}; + +class tst_Factory : public QObject +{ + Q_OBJECT + +private slots: + void testSingleArg() + { + class Egg : public Food + { + public: + explicit Egg(int amount) + : Food(amount) + { qDebug().nospace().noquote() << "Constructor."; } + QDate expireDate() const { + return QDate::currentDate().addDays(1); + } + private: + QDate m_expireDate; + }; + class EggStore : public KDGenericFactory<Food, QString, int> + { + public: + static EggStore &instance() { + static EggStore factory; + return factory; + } + }; + + EggStore::instance().registerProduct<Egg>("Egg"); + QCOMPARE(EggStore::instance().containsProduct("Egg"), true); + // EggStore::instance().registerProduct<Bread>("Bread"); // Does not compile. + + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + auto food = EggStore::instance().create("Egg", 100); + QCOMPARE(food->available(), 100); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(1)); + + QTest::ignoreMessage(QtDebugMsg, "Create function."); + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + EggStore::instance().registerProduct("Egg", &Egg::create<Egg, int>); + food = EggStore::instance().create("Egg", 10); + QCOMPARE(food->available(), 10); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(1)); + + auto lambda = [](int amount) -> Food* { return new Egg(amount); }; + EggStore::instance().registerProduct("Egg", lambda); + + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + food = EggStore::instance().create("Egg", 20); + QCOMPARE(food->available(), 20); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(1)); + } + + void testMultiArg() + { + FoodStore::instance().registerProduct<Bread>("Bread"); + FoodStore::instance().registerProduct("Butter", &Butter::create); + FoodStore::instance().registerProduct<ColdCuts>("ColdCuts"); + // FoodStore::instance().registerProduct<Eggs>("Eggs"); // Does not compile. + + QCOMPARE(FoodStore::instance().containsProduct("Bread"), true); + QCOMPARE(FoodStore::instance().containsProduct("Butter"), true); + QCOMPARE(FoodStore::instance().containsProduct("ColdCuts"), true); + QCOMPARE(FoodStore::instance().containsProduct("Sandwich"), false); + + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + auto food = FoodStore::instance().create("Bread", 10, QDate::currentDate().addDays(7)); + QCOMPARE(food->available(), 10); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(7)); + + QTest::ignoreMessage(QtDebugMsg, "Create function."); + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + food = FoodStore::instance().create("Butter", 2, QDate::currentDate().addDays(3)); + QCOMPARE(food->available(), 2); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(3)); + + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + food = FoodStore::instance().create("ColdCuts", 50, QDate::currentDate().addDays(5)); + QCOMPARE(food->available(), 50); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(5)); + + food = FoodStore::instance().create("Sandwich", 50, QDate::currentDate()); + QCOMPARE(food == Q_NULLPTR, true); + + // overwrites the existing registration + FoodStore::instance().registerProduct("ColdCuts", &ColdCuts::create<ColdCuts, int, QDate>); + QTest::ignoreMessage(QtDebugMsg, "Create function."); + QTest::ignoreMessage(QtDebugMsg, "Constructor."); + food = FoodStore::instance().create("ColdCuts", 100, QDate::currentDate().addDays(4)); + QCOMPARE(food->available(), 100); + QCOMPARE(food->expireDate(), QDate::currentDate().addDays(4)); + } +}; + +QTEST_MAIN(tst_Factory) + +#include "tst_factory.moc" diff --git a/tests/auto/installer/installer.pro b/tests/auto/installer/installer.pro index 0bd5e8690..dfdb23995 100644 --- a/tests/auto/installer/installer.pro +++ b/tests/auto/installer/installer.pro @@ -17,4 +17,5 @@ SUBDIRS += \ packagemanagercore \ settingsoperation \ task \ - clientserver + clientserver \ + factory |