Elegant factory design?
-
I find that I sometimes am using factories to create instances of a certain set of classes (all inheriting from the same base class that inherits QObject). However, the factories are also used to query what objects are available in the first place, and what their user-readable names are. That is very convenient.
The problem I am facing, is that information on the classes seems to be spreading around. The classes themselves have methods to query them, but they are also needed from the factory where the instances are not available yet to provide information on the available types. Also, some information gets repeated between functions of the factory, such as which concrete classes exist. This information is repeated between the method that provides that list, and the method that actually produces which has a big swich statement.
I don't like that.
I am looking for a factory design that is a bit more elegant. The ideal design would:
have minimal impact on the design and implementation of the concrete classes
not need to have instances of all the classes before they are requested
provide an id (int or enum) for each class
provide a translatable, user-readable name for each class (a QString)
does not repeat the information on the classes
as a bonus: is reusable, so I don't have to write the same boilerplate over and over again...
I would be interested to see how others solve this issue, and perhaps what elegant solutions are possible with a bit of QMetaObject trickery for instance.
-
Maybe the factory pattern introduced by loki(modern C++ design)
could ease your pain, you could add or alter the policies with your
need.Here are the codes copy from loki.I alter it a little bit(param names, and error handling)
@
namespace{template<typename IdentifierType, typename ProductType>
class DefaultFactoryError
{typedef typename param_type<IdentifierType>::type IdentifierParam;
public:
ProductType* on_unknown_type(IdentifierParam id)
{
return nullptr;
}
};}
template
<
typename AbstractProduct,
typename IdentifierType,
typename ProductCreator,
template<typename, typename>
class FactoryErrorPolicy = DefaultFactoryErrorclass factory : public FactoryErrorPolicy<IdentifierType, ProductCreator>
{typedef typename param_type<IdentifierType>::type IdentifierParam;
typedef typename param_type<ProductCreator>::type ProductCreatorParam;public:
bool register_type(IdentifierParam id, ProductCreatorParam creator)
{
return associations_.insert(std::make_pair(id, creator) );
}bool unregister_type(IdentifierParam id) { return associations_.erase(id) == 1; } AbstractProduct* create_object(IdentifierParam id) { auto const it = associations_.find(id); if(it != associations_.end() ) { return (it->second)(); } return on_unknown_type(id); }
private:
typedef std::map<IdentifierType, AbstractProduct> AssocMap;
AssocMap associations_;
};
@1 : have minimal impact on the design and implementation of the concrete classes
I don't know this design meet your first requirements or not, but you only need to
change the policy you need with this factory.2 : not need to have instances of all the classes before they are requested
yap, this is what template behavior3 : provide an id (int or enum) for each class
As your wish, you could register each object with different id.
If you need extra informations, you could add a new data member for the factory;change
the value tyoe of the map;add one more policy for you requirement4 : provide a translatable, user-readable name for each class (a QString)
same as 3, choose any solutions which suit to your case5 : does not repeat the information on the classes
Sorry, but i don't know what is the meaning of this?6 : as a bonus: is reusable, so I don’t have to write the same boilerplate over and over again…
This factory is reuseable, without doubt.Sadly this factory can't work with moc of Qt
-
Thanks for your suggestion. I have the book, so I'll have another look. However, the prospect of adding another policy class to handle the requirements I also listed, is not what am looking forward to. That templates don't play nice with moc is not much of an issue, the factory itself doesn't need to be a QObject.
The solution does satisfy an important requirement, in that it at least doesn't repeat information from the concrete classes in the factory (specifically, which id and which name belongs to which class). I'll have to think more about it. Other solutions are still welcome!
-
Would you mind to share your final results with us?Thanks
-
I'm not quite there yet, but so far I have managed to create a factory that satisfies my first five points. The 6th is doable too, by using some template magic, I think. I'll try to do that too.
The result so far is that my factory doesn't need to be modified at all to add new 'products', and that all I need to do in implementation file of the concrete classes (the products):
add a single declare macro that contains the class name, the id to use and a QObject::tr("name") call to declare the translatable name, and
pass a variable declared by the macro above to the constructor of the base class for the products in the constructor of the subclass.
include the factory header
That is quite a neat result, if I say so myself.
The macro is a bit ugly, but the use is quite nice. It creates 20 lines of code. It basically creates an unnamed namespace, inside which it creates a subclass of a 'Machine' (base class is pure virtual) which has a type(), name(), and a create() method. Then, an instance is created and passed as a shared pointer to the factory via a static method in the factory. On adding the machine to the factory, the factory calls the type() method to retreive the type, and then stores the shared pointer under that type in an internal hash. Only when the name is requested, the name() method is called, and at that time the translators have already been installed. The namespace prevents poluting the global name space with random, potentially clashing variables.
The idea is that a Factory uses a Machine to produce a Product. Hence the naming :-)
Once I manage to generalize it to satisfy point 6 as well, I'll post the resulting code.
Edit: oh, and I am not using any QMetaObject magic. Pure C++ :-)
-
@
class ObjectFactoryInterface
{
public:
virtual QObject *create(QObject *parent) = 0;
};template <typename T>
class ObjectFactoryTemplate : public ObjectFactoryInterface
{
QObject *create(QObject *parent = 0)
{
return new T(parent);
}
};struct ObjectFactoryData
{
ObjectFactoryInterface *factory;
};class ObjectFactory
{
public:
QObject *create(const QString &type, QObject *parent = 0)
{
if (types_.contains(type) == false)
return nullptr;return types_.value(type).factory->create(parent); } void registerType(const QString &type, ObjectFactoryInterface *factory) { if (types_.contains(type) == false) types_.insert(type, ObjectFactoryData{factory}); } QList<QString> types() { return types_.keys(); }
private:
QHash<QString, ObjectFactoryData> types_;
};class ObjectFactoryRegister
{
public:
ObjectFactoryRegister(const QString &type,
ObjectFactoryInterface *factory,
ObjectFactory *instance)
{
instance->registerType(id, factory);
}
};
@
It is completely transparent to the and of the managed objects. The only requirement is to inherit from a common base class, QObject in this case.A type can be simply added to the factory (at static initialization time) by adding
@
static ObjectFactoryTemplate<Type> typeFactory_;
static ObjectFactoryRegister("Type", &typeFactory_, factoryInstance());
@
to the related compilation unit (source file) or (at runtime)
@
static ObjectFactoryTemplate<Type> typeFactory_;
...
factoryInstance()->registerType("Type", &typeFactory_);
@The factory can be used (at runtime) using
@
QList<QString> types = factoryInstance()->types();Type *type = factoryInstance()->create("Type");
@Additional information, like a translatable name, can be added by expanding <code>ObjectFactoryData</code>.
I doesn't rely on the QMetaObject either (which wouldn't be available at static initialization time anyway).
-
There are some interesting tricks in this one, thanks! The template friend thing is especially new for me.
How would you go about adding the translatable name though? The issue of adding it to the ObjectFactoryData is, AFAIK, that it gets instantiated at application startup. That is: before the translations are installed. That's how I came to my current design.
I don't particulary like the use of strings as keys for types, but changing it to an enum is trivial of course. The basis is similar to mine, though I don't see how the objects here know their own name/type. I'd like to be able to query the object for that information, without repeating it. Also, the instance method is missing here, but I guess a standard singleton-like approach is what you are using here? All in all: very nice approach, I can steal some tricks from this one!
-
[quote author="Andre" date="1351889827"]How would you go about adding the translatable name though? The issue of adding it to the ObjectFactoryData is, AFAIK, that it gets instantiated at application startup. That is: before the translations are installed. That's how I came to my current design.[/quote]
Creation and registration can be easily split (I've updated the oversimplified example), although I quite like the idea of grouping related information together. Therefore I prefer the static initialization method, because it allows me to do both, the creation and registration in the same module; another candidate for static initialization is <code>qRegisterMetaType()</code>.
Lazy initialization can be used to handle situations where there needs to be deferred initialization, like translation.
[quote author="Andre" date="1351889827"]The basis is similar to mine, though I don't see how the objects here know their own name/type. I'd like to be able to query the object for that information, without repeating it.[/quote]
The idea is that objects know their own type from QMetaObject, and query additional information from the factory. But I also see good reasons to have this data stored with the object itself, also design-wise.
Unfortunately there is no such thing as <code>virtual static</code> in C++, which would allow us to query this information using polymorphism without instantiating an object. But we can reference the data in the factory, which results in the same behaviour.
@
class ObjectFactoryInterface
{
public:
virtual QObject *create(QObject *parent) = 0;
};template <typename T>
class ObjectFactoryTemplate : public ObjectFactoryInterface
{
QObject *create(QObject *parent = 0)
{
return new T(parent);
}
};class ObjectData
{
public:
ObjectData(const QString &type) : type_(type) { }inline QString type() const { return type_; } QString name() const { if (name_.isEmpty() == true) name_ = QObject::tr(type_.toLocal8Bit().constData()); return name_; }
private:
QString type_;
mutable QString name_;
};class ObjectFactory
{
public:
QObject *create(const QString &type, QObject *parent = 0)
{
if (types_.contains(type) == false)
return nullptr;return types_.value(type).second->create(parent); } void registerType(ObjectData *data, ObjectFactoryInterface *factory) { if (types_.contains(data->type()) == false) types_.insert(data->type(), qMakePair(data, factory)); } QList<QString> types() { return types_.keys(); }
private:
QHash<QString, QPair<ObjectData *, ObjectFactoryInterface *>> types_;
};class ObjectFactoryRegister
{
public:
ObjectFactoryRegister(ObjectData *data,
ObjectFactoryInterface *factory,
ObjectFactory *instance)
{
instance->registerType(data, factory);
}
};
@ Exemplary.<code>ObjectData</code> can be either a static or a static member of the object
@
ObjectData Type::objectData("Type");static ObjectFactoryTemplate<Type> objectFactory_;
static ObjectFactoryRegister objectFactoryRegister_(&Type::objectData, &objectFactory_,
factoryInstance());// or
static ObjectData objectData_("Type");
static ObjectFactoryTemplate<Type> objectFactory_;
static ObjectFactoryRegister objectFactoryRegister_(&objectData_, &objectFactory_,
factoryInstance());
@
and is accesible in the factory
@
ObjectData *objectData = types_.value(type).first;
@
and the object
@
ObjectData &objectData = Type::objectData;
@The exact design is subject to personal and situational preference (for example the factory beeing part of <code>objectData</code> itself, so objects can access it).
It is basically the same concept as yours as far as I have understood it, but using templates instead of the preprocessor.
[quote author="Andre" date="1351889827"]I don't particulary like the use of strings as keys for types, but changing it to an enum is trivial of course.[/quote]
I think it is mostly a matter of personal preference, but it will reduce the complexitiy of your design, especially if the application is extensible through external libraries, which should be able to register their own types.
Numbers will always require you to have an algorithm assigning those numbers, ensuring they are unique across external libraries and consitent between consecutive runs of the application, probably with a different set and load order of the libraries.
[quote author="Andre" date="1351889827"]Also, the instance method is missing here, but I guess a standard singleton-like approach is what you are using here? All in all: very nice approach, I can steal some tricks from this one![/quote]
No, <code>ObjectFactory</code> is not a singleton. I'm not a particular friend of singletons (singularity is a responsibility of the surrounding scope, not the class itself). <code>factoryInstance()</code> is a <code>Q_GLOBAL_STATIC</code>, with a non-static wrapper when used in multiple compilation units or simply a static member.