PDA

View Full Version : Simple yet powerful object factory



mcostalba
23rd August 2007, 12:55
An object factory lets you create objects by a value stored in a variable, as example the name of a class in a QString.

The naive approach is to use a very big switch statment somewhere, making a long history short: this is not a good solution (googling around you will find a lot of reference on why this scheme is to avoid).

I have created a very simple yet powerful object factory. Becasue an example is better then 1000 words here it goes a simple driver program.

In case someone is interested I can post "factory.h" sources.

File main.cpp:


#include <QLabel>
#include <QObject>
#include <QTimer>

/* Works both with Qt3 and Qt4. No fancy Qt meta object info used,
* no QOBJECT macro or other specific Qt stuff. No derivation from
* Qt classes used. The only (easily portable) requirements are for
* a QMap and a QString, all remaining stuff is pure C++ with no
* other libraries involved. Finally no namespace pollution, only
* Register<> and Factory<> templates are visible.
*/
#include "factory.h" // just need to include this header

/* First (and only) thing to do is to 'register' a class with a proper factory
* in one IMPLEMENTATION FILE (not header!). You can choose any file you
* want as long as a declaration of your class is included.
* Probably you may want to register the class at the beginning of the file
* that implements your class, but this is up to you.
*
* The first name in 'Register' template instantation is the class to
* register, the second one is the base class of our hierarchy. We can
* have more hierarchies at the same time.
*
* We can register a class with a factory anywhere in a program, both in file
* scope (will be registered at startup with static data), as we see here, or
* in a function body (will be registered when the function is called).
*/
// teach QWidget's factory how to create a QLabel
static Register<QLabel, QWidget> l("QLabel");

// teach QObject's factory how to create a QTimer
static Register<QTimer, QObject> t("QTimer");

/* Of course you can (better) use an anonymous namespace in
* implemantation files to avoid the 'static' qualifier
*/
namespace {
// no problem in re-registering the same class, old one will be overwritten
Register<QTimer, QObject> w("QTimer");

// we can (optionally) provide our custom 'create' function, see later
QLabel* customCreator() { return new QLabel("Function customCreator() was used"); }
}

int main(int argc, char* argv[]) { // this simple driver program requires Qt4

QApplication app(argc, argv);

// now we can start creating objects, as example a QTimer
QObject* t = Factory<QObject>::instance().create("QTimer");
if (dynamic_cast<QTimer*>(t)) qDebug("It's a timer"); // Yes. It is!

// we can use any string expression to pass the class name to our factory
QString myName = "Label";
QWidget* lbl = Factory<QWidget>::instance().create("Q" + myName);
qDebug("Class of lbl is %s", lbl->metaObject()->className()); // Yes. It's a QLabel


{ // local scope here, could be any function body

// we can use any alias when registering a class with a factory,
// in this case we call 'myObject' a QObject class
Register<QObject, QObject> localVar("myObject");

} // variable 'localVar' goes out of scope here and is deleted...

// ...but we can still create a "myObject" object anywhere in the program
QObject* obj = Factory<QObject>::instance().create("myObject");
qDebug("Class of obj is %s", obj->metaObject()->className()); // it's a QObject


// we can always redefine an object creator, as example using our
// custom one instead of the default supplied...
Register<QLabel, QWidget> dummy("QLabel", &customCreator);

// ...so that any new QLabel will be created with our
// customCreator() function
QLabel* lbl2 = dynamic_cast<QLabel*>
(Factory<QWidget>::instance().create("QLabel"));

// "Function customCreator() was used"
qDebug("%s", lbl2->text().toLatin1().constData());

delete t; // objects can be deleted as usual when no more needed
delete lbl;
delete obj;
delete lbl2;

return 0;
}

fullmetalcoder
23rd August 2007, 12:58
Please use the [ C O D E ] tag... And also think about giving the interesting part : the factory header...:rolleyes:

mcostalba
23rd August 2007, 16:46
That's the interesting part: file factoy.h

The only difference with the example before is that I have added an
helper to create an object:

Before

QObject* t = Factory<QObject>::instance().create("QTimer");

Now simply

QObject* t = new_object<QObject>("QTimer");




/*
Author: Marco Costalba (C) 2005-2007
*/
#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template<class Base> Base* new_object(const QString&);

template<class Base> class Factory { // singleton class, one each base class
Factory() {}
Factory(const Factory&);
Factory& operator=(const Factory&);
~Factory() { qDeleteAll(creators); }

Base* create(const QString& nm) const {
return (creators.contains(nm) ? creators[nm]->create() : NULL);
}
friend Base* new_object<>(const QString&);
public:
class Creator { public:
virtual ~Creator() {}
virtual Base* create() const = 0;
};
static Factory& instance() {
static Factory p_instance;
return p_instance;
}
void unregisterCreator(const QString& nm) {
if (creators.contains(nm)) {
delete creators[nm];
creators.remove(nm);
}
}
void registerCreator(const QString& nm, const Creator* c) {
unregisterCreator(nm);
creators.insert(nm, c);
}
private:
QMap<QString, const Creator*> creators;
};

template<class Base> Base* new_object(const QString& nm) {
return Factory<Base>::instance().create(nm);
}

template<class Derived, class Base> class Register {
typedef Derived* (*CreateFn) ();
class Creator : public Factory<Base>::Creator { public:
Creator(CreateFn fn) : createFn(fn) {}
virtual Derived* create() const {
return (createFn ? createFn() : new Derived(NULL));
};
CreateFn createFn;
};
public:
Register(const QString& nm, CreateFn fn = NULL) {
Factory<Base>::instance().registerCreator(nm, new Creator(fn));
}
};

#endif