QObjectFactory

Factory Design Pattern is a well known pattern. Its a way to organise code so that class-instances can be created at run time against a developer defined key. Factories specialise in create objects of a specific type. For example (yes, this example is inspired from the example on the Wikipedia page about Factory Design Patterns)

#define interface class
#define implements : public

interface IPerson
{
public:
    virtual std::string getType() = 0;
};

class VillagePerson implements IPerson
{
public:
    virtual std::string getType() { return "VillagePerson"; }
};

class TownPerson implements IPerson
{
public:
    virtual std::string getType() { return "TownPerson"; }
};

class CityPerson implements IPerson
{
public:
    virtual std::string getType() { return "CityPerson"; }
};

class PersonFactory
{
public:
    IPerson *createPerson(const std::string &type) {
        if(type == "VillagePerson")
            return new VillagePerson;
        if(type == "TownPerson")
            return new TownPerson;
        if(type == "CityPerson")
            return new CityPerson;
        return nullptr;
    }
};

So, we can now create instances of IPerson using the PersonFactory class. For example:

PersonFactory factory;
IPerson *person = factory.createPerson("CityPerson");

Now person will point to an instance of CityPerson class. Thats the whole point of a factory, to create object instances based on a key. In this case the key was type name as string.

Qt uses Factory Design Pattern in several places. For example the QItemEditorFactory class helps create editor widgets for items in a QAbstractItemView. Over there the type is integer. There is also a QStyleFactory that creates instances of QStyle based on style-name as key.

Almost every software project that I have been a part of makes use of the factory design pattern. Almost all of the factory classes I have written create QObject subclasses based on a QString or QByteArray key. Typically the key is name of the QObject subclass, but its not always the case. After one writes enough number of factories, it becomes boring to write the same thing over and over again. So I came up with a simple solution.

QObjectFactory class

Lets take the same example as before. Suppose we wanted to create a Person factory class. Lets also suppose that we can make peace with the fact that objects created by our factory will be QObject subclasses. So the person interface and subclasses would look like this

class AbstractPerson : public QObject
{
    Q_OBJECT

public:
    AbstractPerson(QObject *parent=nullptr)
        : QObject(parent) { }
    ~AbstractPerson() { }

    virtual QByteArray getType() const = 0;
};

class VillagePerson : public AbstractPerson
{
    Q_OBJECT

public:
    Q_INVOKABLE VillagePerson(QObject *parent=nullptr)
        : AbstractPerson(parent) { }
    ~VillagePerson() { }

    virtual QByteArray getType() const {
        return "VillagePerson";
    }
};

class TownPerson : public AbstractPerson
{
    Q_OBJECT

public:
    Q_INVOKABLE TownPerson(QObject *parent=nullptr)
        : AbstractPerson(parent) { }
    ~TownPerson() { }

    virtual QByteArray getType() const {
        return "VillagePerson";
    }
};

Notice the following

  • AbstractPerson is subclassed from QObject
  • AbstractPerson and all its subclasses have Q_OBJECT macro declared in them
  • AbstractPerson and all its subclasses have a constructor that accepts a parent pointer.
  • Subclasses of AbstractPerson have their constructor marked as Q_INVOKABLE

Now, lets register subclasses of AbstractPerson in a factory.

QObjectFactory factory;
factory.addClass<VillagePerson>();
factory.addClass<TownPerson>();
factory.addClass<CityPerson>();

That’s it. Creation of instances from the factory would be as simple as

AbstractPerson *person = factory.create<AbstractPerson>("CityPerson");

Seems magical right? Let’s look at how the QObjectFactory class is written.

class QObjectFactory
{
public:
    QObjectFactory() { }
    ~QObjectFactory() { }

    void add(const QMetaObject *mo) { m_metaObjects += mo; }
    void remove(const QMetaObject *mo) { m_metaObjects -= mo; }

    template <class T>
    void addClass() { this->add( &T::staticMetaObject ); }

    template <class T>
    void removeClass() { this->remove( &T::staticMetaObject ); }

    const QMetaObject *find(const QByteArray &className) const {
        Q_FOREACH(const QMetaObject *mo, m_metaObjects) {
            if( !qstrcmp(className.data(), mo->className()) )
                return mo;
        }
        return nullptr;
    }

    QObject *create(const QByteArray &className, QObject *parent=nullptr) const {
        const QMetaObject *mo = this->find(className);
        if(mo == nullptr)
            return nullptr;
        QObject *obj = mo->newInstance(Q_ARG(QObject*,parent));
        return obj;
    }

    template <class T>
    T *create(const QByteArray &className, QObject *parent=nullptr) const {
        QObject *obj = this->create(className, parent);
        return qobject_cast<T*>(obj);
    }

private:
    QSet<const QMetaObject*> m_metaObjects;
};

Qt’s QMetaObject class provides a newInstance() method that helps us call the constructor of its QObject class using a transparent API. By using some really simple template constructs, we make the QObjectFactory class look magical.

Can we use QObjectFactory for creating widgets?

QWidget and subclasses accept a QWidget* as parent pointer, instead of QObject*. So, the QObjectFactory class in its current incarnation cannot be used as a QWidget factory. We will need to write a QWidgetFactory class, that does pretty much the same thing, just that the parent pointer will be of type QWidget*. Or, we can do something smarter.

template <class T>
class QtFactory
{
public:
    QtFactory() { }
    ~QtFactory() { }

    void add(const QMetaObject *mo) { m_metaObjects += mo; }
    void remove(const QMetaObject *mo) { m_metaObjects -= mo; }

    template <class Class>
    void addClass() { this->add( &Class::staticMetaObject ); }

    template <class Class>
    void removeClass() { this->remove( &Class::staticMetaObject ); }

    const QMetaObject *find(const QByteArray &className) const {
        Q_FOREACH(const QMetaObject *mo, m_metaObjects) {
            if( !qstrcmp(className.data(), mo->className()) )
                return mo;
        }
        return nullptr;
    }

    T *create(const QByteArray &className, T *parent=nullptr) const {
        const QMetaObject *mo = this->find(className);
        if(mo == nullptr)
            return nullptr;

        const char *t = this->type();
        QObject *obj = qobject_cast<T*>(mo->newInstance( QArgument<T*>(t,parent) ));
        return qobject_cast<T*>(obj);
    }

    template <class Class>
    Class *create(const QByteArray &className, T *parent=nullptr) const {
        T *obj = this->create(className, parent);
        return qobject_cast<Class*>(obj);
    }

private:
    const char *type() const {
        static const char *qobjectstar = "QObject*";
        static const char *qwidgetstar = "QWidget*";
        if( typeid(T) == typeid(QWidget) )
            return qwidgetstar;
        return qobjectstar;
    }

private:
    QSet<const QMetaObject*> m_metaObjects;
};

typedef QtFactory<QObject> QObjectFactory;
typedef QtFactory<QWidget> QWidgetFactory;

Notice how QtFactory is now a generic template class and we create QObjectFactory and QWidgetFactory from it. Now we can use QWidgetFactory as follows

class LineEdit : public QLineEdit
{
    Q_OBJECT

public:
    Q_INVOKABLE LineEdit(QWidget *parent=nullptr)
        : QLineEdit(parent) { }
    ~LineEdit() { }
};

class PushButton : public QPushButton
{
    Q_OBJECT

public:
    Q_INVOKABLE PushButton(QWidget *parent=nullptr)
        : QPushButton(parent) {
        this->setText("Button");
    }
    ~PushButton() { }
};

int main(int argc, char **argv)
{
    QApplication a(argc, argv);

    QWidgetFactory factory;
    factory.addClass<LineEdit>();
    factory.addClass<PushButton>();

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    layout->addWidget(factory.create("LineEdit", &window));
    layout->addWidget(factory.create("PushButton", &window));

    window.show();

    return a.exec();
}

How about key as something other than class name?

So far we are using class-name as the lookup key in the factory class. What if we wanted to use as key something other class-names? For example, we may want a widget-factory that helps us create widgets based on the type of data we want to edit using it. We can make use of Q_CLASSINFO() to add additional data about the widget class into its QMetaObject as follows.

class LineEdit : public QLineEdit
{
    Q_OBJECT
    Q_CLASSINFO("DataTypes", "QString;QUrl")

public:
    Q_INVOKABLE LineEdit(QWidget *parent=nullptr)
        : QLineEdit(parent) { }
    ~LineEdit() { }
};

class CheckBox : public QCheckBox
{
    Q_OBJECT
    Q_CLASSINFO("DataTypes", "bool")

public:
    Q_INVOKABLE CheckBox(QWidget *parent=nullptr)
        : QCheckBox(parent) {
        this->setText("Yes/No");
    }
    ~CheckBox() { }
};

Now, we update the QtFactory class to lookup based on the semi-colon separated list in value to DataTypes Q_CLASSINFO.

template <class T>
class QtFactory
{
public:
    QtFactory(const QByteArray &key=QByteArray()) : m_classInfoKey(key) { }
    ~QtFactory() { }

    QByteArray classInfoKey() const { return m_classInfoKey; }

    void add(const QMetaObject *mo) {
        QList<QByteArray> keys;
        const int ciIndex = mo->indexOfClassInfo(m_classInfoKey.constData());
        if(ciIndex >= 0) {
            const QMetaClassInfo ci = mo->classInfo(ciIndex);
            keys = QByteArray(ci.value()).split(';');
        }
        keys.append( QByteArray(mo->className()) );
        m_metaObjects.insert(mo, keys);
        Q_FOREACH(QByteArray key, keys)
            m_keyMap[key].append(mo);
    }

    void remove(const QMetaObject *mo) {
        const QList<QByteArray> keys = m_metaObjects.take(mo);
        Q_FOREACH(QByteArray key, keys) {
            QList<const QMetaObject *> mos = m_keyMap.take(key);
            mos.removeOne(mo);
            if(mos.isEmpty())
                continue;
            m_keyMap[key] = mos;
        }
    }

    // ...

    QList<QByteArray> keys() const { return m_keyMap.keys(); }

    const QMetaObject *find(const QByteArray &val) const {
        const QList<const QMetaObject *> mos = m_keyMap.value(val);
        return mos.isEmpty() ? nullptr : mos.last();
    }

    // ...

private:
    QMap<const QMetaObject*, QList<QByteArray> > m_metaObjects;
    QMap<QByteArray, QList<const QMetaObject *> > m_keyMap;
    QByteArray m_classInfoKey;
};

Notice how the factory class now uses both class-names and Q_CLASSINFO data to lookup which class to create an instance of.

Now we can use the QWidgetFactory class as follows.

int main(int argc, char **argv)
{
    QApplication a(argc, argv);

    QWidgetFactory factory("DataTypes");
    factory.addClass<LineEdit>();
    factory.addClass<CheckBox>();

    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    layout->addWidget(factory.create("QString", &window));
    layout->addWidget(factory.create("QUrl", &window));
    layout->addWidget(factory.create("bool", &window));

    window.show();

    return a.exec();
}

The entire widget factory is just a header file. You can expand this block to view/copy the entire code.

QtFactory Source Code
/****************************************************************************
**
** Copyright (C) Prashanth Udupa, Bengaluru
** Email: prashanth.udupa@gmail.com
**
** This code is distributed under LGPL v3. Complete text of the license
** can be found here: https://www.gnu.org/licenses/lgpl-3.0.en.html
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#ifndef QOBJECTFACTORY_H
#define QOBJECTFACTORY_H

#include <QSet>
#include <QMap>
#include <QWidget>
#include <QObject>
#include <QMetaObject>
#include <QMetaClassInfo>

template <class T>
class QtFactory
{
public:
    QtFactory(const QByteArray &key=QByteArray()) : m_classInfoKey(key) { }
    ~QtFactory() { }

    QByteArray classInfoKey() const { return m_classInfoKey; }

    void add(const QMetaObject *mo) {
        QList<QByteArray> keys;
        const int ciIndex = mo->indexOfClassInfo(m_classInfoKey.constData());
        if(ciIndex >= 0) {
            const QMetaClassInfo ci = mo->classInfo(ciIndex);
            keys = QByteArray(ci.value()).split(';');
        }
        keys.append( QByteArray(mo->className()) );
        m_metaObjects.insert(mo, keys);
        Q_FOREACH(QByteArray key, keys)
            m_keyMap[key].append(mo);
    }
    void remove(const QMetaObject *mo) {
        const QList<QByteArray> keys = m_metaObjects.take(mo);
        Q_FOREACH(QByteArray key, keys) {
            QList<const QMetaObject *> mos = m_keyMap.take(key);
            mos.removeOne(mo);
            if(mos.isEmpty())
                continue;
            m_keyMap[key] = mos;
        }
    }

    template <class Class>
    void addClass() { this->add( &Class::staticMetaObject ); }

    template <class Class>
    void removeClass() { this->remove( &Class::staticMetaObject ); }

    QList<QByteArray> keys() const { return m_keyMap.keys(); }

    const QMetaObject *find(const QByteArray &val) const {
        const QList<const QMetaObject *> mos = m_keyMap.value(val);
        return mos.isEmpty() ? nullptr : mos.last();
    }

    T *create(const QByteArray &className, T *parent=nullptr) const {
        const QMetaObject *mo = this->find(className);
        if(mo == nullptr)
            return nullptr;
        const char *t = this->type();
        QObject *obj = qobject_cast<T*>(mo->newInstance( QArgument<T*>(t,parent) ));
        return qobject_cast<T*>(obj);
    }

    template <class Class>
    Class *create(const QByteArray &className, T *parent=nullptr) const {
        T *obj = this->create(className, parent);
        return qobject_cast<Class*>(obj);
    }

private:
    const char *type() const {
        static const char *qobjectstar = "QObject*";
        static const char *qwidgetstar = "QWidget*";
        if( typeid(T) == typeid(QWidget) )
            return qwidgetstar;
        return qobjectstar;
    }

private:
    QMap<const QMetaObject*, QList<QByteArray> > m_metaObjects;
    QMap<QByteArray, QList<const QMetaObject *> > m_keyMap;
    QByteArray m_classInfoKey;
};

typedef QtFactory<QObject> QObjectFactory;
typedef QtFactory<QWidget> QWidgetFactory;

#endif // QOBJECTFACTORY_H

Leave a Reply

Your email address will not be published. Required fields are marked *