📜 ⬆️ ⬇️

Reasoned factory

Good day!

Task

In the current project, I faced the need to generate inheritance objects from a common interface, while generating successors is necessary by passing them some constructor initializer to them. The default constructor of classes may not be. At the same time, a specific object factory is defined in dynamically loadable plug-ins, therefore it must have a specific interface.
I can offer two ways to solve this problem.

Initial data

Suppose the factory interface should look like this:

class IFactoryBasic { public: IFactoryBasic() {} virtual ~IFactoryBasic() {} virtual Test* create(const QString &key, const QString &args)=0; }; 

')
where Test is a certain base class

 class Test { protected: QString _word; public: Test(const QString &word):_word(word) {} virtual ~Test() {} virtual void test(){ qDebug()<<"test "<<_word; } }; class TestChild: public Test { public: TestChild(const QString &word): Test(word) {} virtual void test() { qDebug()<<"test child"<<_word; } }; 


TestChild - Test heir
Both classes accept the string parameter word in the constructor, which we can then verify in the test() function.

First way

The way is simple. It is based on creating a template framework for a future factory.

 template<class T, class C, class A> class IFactory { QMap<QString, C* (T::*)(const A&) > handler; protected: void add(const QString &key, C *(T::*func)(const A &)) { handler[key]=func; } public: IFactory() {} virtual ~IFactory() {} C *make(const QString &key, const A &args) { if(handler.contains(key)) { T* inheritor = dynamic_cast<T*>(this); if(inheritor) return (inheritor->*handler[key])(args); } return 0; } }; 


There is a small obligation for class users. The first template parameter must be a class that inherits from IFactory. Further there will be explanations for what it was needed.
handler IFactory class IFactory is an associative container containing a key and the corresponding object creation function. The signature of the spawning function is described as C* (T::*)(const A&) , that is, the return value will have a pointer to a certain C class, and the function argument is passed a reference to an object of type A
The add(...) function adds a key-function pair <key,func> to the container.
The function make(...) calls the spawn function, if it exists in the container (after dynamically transforming the pointer type of this to the type of the heir, otherwise you cannot call functions that were defined there).
This is the main frame of the factory, it remains to describe a specific factory

 class FactoryFst: public IFactory<FactoryFst, Test, QString>, public IFactoryBasic { Test *createOrigin(const QString &args){ return new Test(args); } Test *createChild(const QString &args) { return new TestChild(args); } public: FactoryFst() { add("test", &FactoryFst::createOrigin); add("testchild", &FactoryFst::createChild); } Test *create(const QString &key, const QString &args) { return make(key, args); } }; 


It is easy to guess that we use multiple inheritance to meet the requirements of the IFactoryBasic interface. For the other parent, we explicitly specify the heir of the FactoryFst , the returned pointer will be a pointer to an object of the Test class, and a reference to the QString object is passed as an argument.
In accordance with this definition, functions are generated that generate objects of the type Test and TestChild :
Test *createOrigin(const QString &args){ return new Test(args); } Test *createOrigin(const QString &args){ return new Test(args); } - creates an object of type Test, passing the argument QString to it in the constructor.
Test *createChild(const QString &args) { return new TestChild(args); } Test *createChild(const QString &args) { return new TestChild(args); } - similarly creates an object of type TestChild .
It remains only to register these functions in the FactoryFst constructor and define the create(...) IFactoryBasic interface.

Second way

This method mostly uses templates.
To build a factory, we need a little preparation. First you need to define some of the classes used by the factory.

To begin with, we define an auxiliary class for storing the values ​​of the argument passed. In order to allow the use of pointers to this class without hindrance, let's make it inheritable from a non-generic base class.

 class Arguments { public: virtual ~Arguments() {} }; template<class T> class TArguments: public Arguments { public: T arg; TArguments(T _arg):arg(_arg) {} }; 


Arguments is the base class, TArguments is the template class for storing the transmitted object.

We also need a template wrapper class to call the operator new .

 class Container { public: virtual ~Container() {} virtual void *make( Arguments* ) = 0; }; 


A non-template Container serves the same purpose as Arguments . So that we could always call the spawning function make(...) for any of its template heirs. The make(...) function should return a pointer to the created object.
 template<class T, typename A> class TContainer: public Container { public: void *make(Arguments* args=0) { TArguments<A>* a = dynamic_cast< TArguments<A>* >( args ); if(!a) return 0; return new T(a->arg); } }; 


The TContainer class TContainer already template, the type of the returned pointer T and the type of the argument for the constructor A passed to it as template arguments.
In the function make(...) , the pointer to Arguments is passed as an argument, but we understand that in fact it must be a pointer to TArguments and try to dynamically convert the type. If all conversions were successful, we can create an object of a previously defined type.

As a result, the frame of the factory will look like this:

 template<typename C=void, typename A=void> class TemplateFactory { QMap<QString, Container*> handler; public: TemplateFactory() {} virtual ~TemplateFactory(){ qDeleteAll(handler.values()); } template<class T> void add(const QString &name) { handler.insert(name, new TContainer<T, A>()); } C *make(const QString &name, const A &arg) { if(handler.contains(name)) return static_cast<C*>(handler.value(name)->make(new TArguments<A>(arg))); return 0; } }; 


Here, the arguments of template C are the base class, A is the argument of the constructor of the objects being generated.
The add(...) function registers new classes in the list, make(...) creates a class object, passing the function a key to select the type and the argument of the constructor to the function. Type static_cast used to convert the type void* to the desired C* .
Everything is ready to create a specific factory.

 class FactorySnd: public TemplateFactory<Test, QString>, public IFactoryBasic { public: FactorySnd() { add<Test>("test"); add<TestCild>("testchild"); } Test* create(const QString &name, const QString &arg){ return make(name, arg); } }; 


Again, multiple inheritance is used, the create(...) function is overridden. In the constructor, classes are registered.

Result


Both factories work in what can be ascertained by executing the following code
  IFactoryBasic* factory = new FactorySnd(); Test* test = factory->create("test", "A"); test->test(); test = factory->create("testchild", "B"); test->test(); delete factory; factory = new FactoryFst(); Test *stest = factory->create("test", "C"); stest->test(); stest = factory->create("testchild", "D"); stest->test(); 


In the console we get the following exhaust:
 test "A" test child "B" test "C" test child "D" 


Conclusion

The same problem, of course, can have more than one solution. The solutions presented are not the only and infallible. The first method gives more freedom, since it gives the right to define methods for generating objects, because in the same method, you can link this object with signals (if it is a QObject) heir QObject) or register it with an Observer. You can also somehow modify the argument passed to the generating method before passing it to the constructor. But the charge for this is more complex code maintenance, the addition of a new generated object. The second method is less demanding in this respect, but leaves less freedom for the user. That is, the creation of an object occurs as described in the base class and nothing else. Also, factories are focused on creating objects inherited from one class and taking one object as an argument. In this initializer object, all necessary properties must be encapsulated to create a new object. This, of course, is not universal (it imposes certain restrictions on the objects being created), but it is quite suitable for solving an applied problem.

Source: https://habr.com/ru/post/192520/


All Articles