⬆️ ⬇️

Put the objects on the stream, the pattern of the factory of objects

Good day, reader. I want to share with you the knowledge of one of the patterns I use most often - the factory of objects, for this pattern another name is also appropriate - the virtual designer.



What is this pattern?



Proceeding from the name, it is easy to guess that this is a certain, definite factory (or factory) that creates objects. Once again I will express the word definite. As in real life, the factory has a certain specialization, creating products or devices of a particular type. And the factory, which produces, for example, furniture, can not produce, for example, also components for smartphones. By analogy with programming, a factory of objects can only create objects of a certain type that use a single interface. The main advantages of this pattern in C ++ are the simplified creation of objects of various classes using a single interface. Often, libraries that develop programmers need not only to be able to work with certain objects, but also to create them. The most obvious example is downloading files of various formats. We do not know which file we will upload in advance, but we have a list of possible formats. The user specifies the file, the library tries to determine the file type and call the appropriate loader for it. In most cases, programmers use constructions like switch or if to determine which class instance they need to create. And the more possible options appear, the more this structure expands, later turning into an ugly monster.

')

What can a factory of objects offer us?

First, a simple method of creating objects, which will reduce the switch / if to the 1st line.

Secondly, convenient methods for working with factory objects. We can always know for sure whether a particular class is registered with it, the number of registered classes, as well as convenient methods for adding and removing instances of classes in a factory. With the help of the factory, you can limit the set of possible classes that it will create using a certain configuration.





Creating a concrete factory



To begin with, we implement a specific factory, without using templates, in order to understand how it should work.

And so, we need a class that can create objects that support a single interface. Let's call this interface Foo, and the factory, respectively, FooFactory. As an identifier to create an object, take a string.



lass FooFactory { public: FooFactory(); ~virtual FooFactory(); Foo * create(const std::string & id) const; }; 


And so, so far everything is fine, but only a couple of lines of code are written. And how are we going to add classes and actually create their instances? Let us examine the problems in order. Before you create an object, you need to add a class to the factory. Obviously, we need some kind of function. But how to transfer a class to a function, not an object?



Alternatively, you can use the cloning strategy. That is, to create an object, place it in a factory, and when calling the create method of a factory, call a function of type clone (). The option is frankly a bad one, which forces the programmer not only to add the clone function to the Foo interface, and to implement it in all specific classes. And besides, when adding an object to the factory, we will need to create it — that is, allocate memory for the object. And objects can be quite heavy. Yes, of course, in the modern world, the problem of memory / performance is not so acute, but C ++ is such a language that allows optimizing both high-level and low-level constructions, and why not use it.



Another option is when adding a class to a factory, passing a pointer to a function to the method that will create an object of the type you need. In this case, there are even more problems than in the previous version. It will be necessary to implement this function for each specific class being created, and when adding a class to the factory, pass this function as a parameter. You can of course make macros, but it will look very unattractive, not to mention the fact that many believe that macros in C ++ are evil.



But on the other hand, C ++ has templates that will help solve this not simple task. To create objects of type Foo, we will use other auxiliary objects that will take on the job of creating objects of the class we need. In common, such classes are called creator or instantiator. An abstract creator class is created, which usually has only one method — the creation of an object of a specific interface; And on its basis concrete classes creators are already created. Thanks to the abstract creator class, we can store a set of concrete creators in any container. In this case, the templates will play the role of macros, allowing you to generate code for a specific creator based on the template parameter.



The abstract class for creating objects of type Foo:



 class abstractFooCreator { public: virtual fooCreator() {} virtual Foo * create() const = 0; }; 


And a template class in which the actual code will be generated to create an object of a particular class:



 template <class C> class fooCreator : public abstractFooCreator { public: virtual Foo * create() const { return new C(); } }; 


Thus, we can already write our template method to add a class to the factory. The only thing we need is to choose a container for storing our creators. The obvious choice is std :: map.



 typedef std::map<std::string, abstractFooCreator*> FactoryMap; FactoryMap _factory; template <class C> void add(const std::string & id) { typename FactoryMap::iterator it = _factory.find(id); if (it == _factory.end()) _factory[id] = new fooCreator<C>(); } 


Now we have the first working version of the factory, in which you can add classes that support the Foo interface.

Well, in fact, our 2nd problem, namely the creation of objects of the desired type, has practically already been solved, since in fact it depended only on how we will store the creators.



 void Foo * create(const std::string & id) const { typename FactoryMap::iterator it = _factory.find(id); if (it != _factory.end()) return it->second->create(); return 0; } 




Collect all the code to see the full picture.



Expand
 class abstractFooCreator { public: virtual fooCreator() {} virtual Foo * create() const = 0; }; template <class C> class fooCreator : public abstractFooCreator { public: virtual Foo * create() const { return new C(); } }; lass FooFactory { protected: typedef std::map<std::string, abstractFooCreator*> FactoryMap; FactoryMap _factory; public: FooFactory(); ~virtual FooFactory(); template <class C> void add(const std::string & id) { typename FactoryMap::iterator it = _factory.find(id); if (it == _factory.end()) _factory[id] = new fooCreator<C>(); } Foo * create(const std::string & id) { typename FactoryMap::iterator it = _factory.find(id); if (it != _factory.end()) return it->second->create(); return 0; } }; 






Well, a small example of how to use this factory. Adding classes to the factory:



 FooFactory factory; factory.add<MyFoo>("MyFoo"); factory.add<MyFoo2>("MyFoo2"); factory.add<ImprovedFoo>("ImprovedFoo"); 


Creating an object with a factory:



 Foo * p = factory.create("MyFoo2"); 


So simply with the help of the factory you can manage the creation of objects that support a single interface.



Creating a template factory



Well, now we will create, on the basis of this particular example, a very specific pattern.



What does our factory need to satisfy our capabilities? Of course, this is an assignment of an identifier type, which can be either an enum or std :: string or any other type suitable for an identifier. The second is the actual type of objects that we will create, whose role in the example above was performed by the Foo class.



 template <class Base, class IdType> class ObjectFactory { public: ObjectFactory() {} virtual ~ObjectFactory(); template <class C> void add(const IdType & id); Base * create() const; }; 


This is what our template factory will look like.

We proceed to its implementation. As in the Foo example, we need to solve the problem of creating objects of a particular type. That is, we need an abstract class that has a method for creating an object of type Base, and a concrete creator class that inherits this abstract class and implements this method using a template parameter.



 template <class Base> class AbstractCreator { public: AbstractCreator() {} virtual ~AbstractCreator(){} virtual Base* create() const = 0; }; template <class C, class Base> class Creator : public AbstractCreator<Base> { public: Creator() { } virtual ~Creator() {} Base * create() const { return new C(); } }; 


Well, now you just need to use these classes in our factory.



 template <class Base, class IdType> class ObjectFactory { protected: typedef AbstractCreator<Base> AbstractFactory; typedef std::map<IdType, AbstractFactory*> FactoryMap; FactoryMap _factory; public: ObjectFactory() {} virtual ~ObjectFactory(); template <class C> void add(const IdType & id) { registerClass(id, new Creator<C, Base>()); } protected: void registerClass(const IdType & id, AbstractFactory * p) { typename FactoryMap::iterator it = _factory.find(id); if (it == _factory.end()) _factory[id] = p; else delete p; } }; 


What was significant compared to the example of Foo? Of course, this is the registerClass method, which takes as an argument an object of the AbstractFactory type, which, in turn, can create objects of that class, which we specify as a template parameter for the add method.



Add the ability to set the behavior of the factory



I will omit, so far the creation of the remaining simple methods, such as checking the existence of the desired class in the factory, their number and deletion - a full listing can be viewed at the end. I propose to make the factory even more flexible. Indeed, besides the fact that we set the type of the identifier for the factory and the base class, the interface of which must be implemented by the classes placed in the factory, we can also add a customization of the behavior of our factory in case of certain errors. What if the requested type is not in the factory at creation? Return zero or throw an exception? What should I do when I try to delete a nonexistent class or when I try to add an already registered object to the factory? To be able to change the behavior of the factory, we need to use a different design pattern — a strategy, also known as a policy. This pattern is very well covered in the book A. Alexandrecu “Modern Design in C ++”.



In projects that I support is necessary, so far, only 2 behaviors. Failure to respond to an error, and throwing a special type of exception, used in my projects. The only parameter that is needed for debugging and in general for a detailed description of the error is the identifier, which we pass to the functions of creation, deletion, etc. Thus, our policy interface should look something like this:



 template <class Base, class Type> class ObjectFactoryIgnoreErrorPolicy { public: Base * onCreateFailed(const Type & type) const { return 0; } void onRemoveFailed(const Type & type) {} void onDuplicateRegistered(const Type & type) {} }; 




In addition to the actual functions that implement the behavior, the policy class also requires the type of interface that implements the factory and the type of identifier for which the error occurred and which is passed to all methods. By analogy with IgnoreErrorPolicy, I will cite as an example the same policy that throws an exception in case of an error.



Implementation example
 class ObjectFactoryException : public std::exception { std::string _msg; public: ObjectFactoryException(const std::string & msg) throw() : _msg(msg) {} virtual ~ObjectFactoryException() throw() {} virtual const char * what() const throw() { return _msg.c_str(); } }; template <class Base, class Type> class ObjectFactoryThrowExceptionErrorPolicy { public: std::string generateMessage(const char * msg, const Type & type) const { std::stringstream strm; strm << msg << ", requested type id : " << type; return strm.str(); } Base * onCreateFailed(const Type & type) const { throw ObjectFactoryException(generateMessage("ObjectFactory - can't create object (not registered)", type)); } void onRemoveFailed(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - can't remove class (not registered)", type)); } void onDuplicateRegistered(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - class already registered", type)); } }; 




Now let's build our policy into the factory. This can be done in several ways - simply inherit from the policy class, or use the policy class as a factory attribute. In both cases, the only problem is how to set the template parameters for the policy class from the factory class. This will help us a great feature of C ++ - template template parameters. When declaring a factory class, we indicate that we want to pass as a parameter not just some class, but a template class. It will look like this:



 template <class Base, class IdType, template <class, class> class ObjectFactoryErrorPolicy = ObjectFactoryIgnoreErrorPolicy > class ObjectFactory : public ObjectFactoryErrorPolicy<Base, IdType> { … 


As the 3rd parameter, the template <class, class> class is specified ...

Immediately, a default policy is set for this parameter. If you need to set a different policy, then when typedef a factory you need to specify the name of the class with the necessary policy as the last parameter, for example:



 typedef ObjectFactory<Foo, FooType, ObjectFactoryThrowExceptionPolicy> FooFactory; 


In my case, I use the error ignoring policy much more often, so I set it as a default parameter. You can go even further and make the policy dynamic, with the ability to change it at runtime, but for me at the moment this is not relevant and so far in my small practice such a mechanism has not been required. Actually for this reason I chose the method of inheritance from the class of the policy, and not using it as an attribute of the factory.



Listing





Well, now is the time to take stock and present a full listing of the pattern with my implementation.

listing
 #include <map> #include <string> #include <memory> #include <sstream> namespace grs { /*  ,       */ template <class Base> class AbstractCreator { public: AbstractCreator() { } virtual ~AbstractCreator() { } virtual Base* create() const = 0; private: AbstractCreator(const AbstractCreator&); AbstractCreator& operator = (const AbstractCreator&); }; template <class C, class Base> class Creator : public AbstractCreator<Base> { public: Creator() { } virtual ~Creator() { } Base * create() const { return new C(); } }; /*    */ class ObjectFactoryException : public std::exception { std::string _msg; public: ObjectFactoryException(const std::string & msg) throw() : _msg(msg) {} virtual ~ObjectFactoryException() throw() {} virtual const char * what() const throw() { return _msg.c_str(); } }; template <class Base, class Type> class ObjectFactoryIgnoreErrorPolicy { public: Base * onCreateFailed(const Type & type) const { return 0; } void onRemoveFailed(const Type & type) { } void onDuplicateRegistered(const Type & type) { } }; template <class Base, class Type> class ObjectFactoryThrowExceptionErrorPolicy { public: std::string generateMessage(const char * msg, const Type & type) const { std::stringstream strm; strm << msg << ", requested type id : " << type; return strm.str(); } Base * onCreateFailed(const Type & type) const { throw ObjectFactoryException(generateMessage("ObjectFactory - can't create object (not registered)", type)); } void onRemoveFailed(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - can't remove class (not registered)", type)); } void onDuplicateRegistered(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - class already registered", type)); } }; /*  */ template <class Base, class IdType = int, template <class, class> class ObjectFactoryErrorPolicy = ObjectFactoryIgnoreErrorPolicy > class ObjectFactory : public ObjectFactoryErrorPolicy<Base, IdType> { protected: typedef AbstractCreator<Base> AbstractFactory; typedef std::map<IdType, AbstractFactory*> FactoryMap; public: ObjectFactory() { } virtual ~ObjectFactory() { for (typename FactoryMap::iterator it = _map.begin(), endIt = _map.end(); it != endIt; ++it) delete it->second; } Base * create(const IdType & id) const { typename FactoryMap::const_iterator it = _map.find(id); if (it != _map.end()) return it->second->create(); return onCreateFailed(id); } template <class C> void add(const IdType & id) { registerClass(id, new Creator<C, Base>); } void remove(const IdType & id) { typename FactoryMap::iterator it = _map.find(id); if (it != _map.end()) { delete it->second; _map.erase(it); } else onRemoveFailed(id); } bool isRegistered(const IdType & id) const { return _map.find(id) != _map.end(); } size_t size() const { return _map.size(); } protected: void registerClass(const IdType & id, AbstractFactory * pAbstractFactory) { std::auto_ptr<AbstractFactory> ptr(pAbstractFactory); typename FactoryMap::iterator it = _map.find(id); if (it == _map.end()) _map[id] = ptr.release(); else onDuplicateRegistered(id); } private: ObjectFactory(const ObjectFactory&); ObjectFactory& operator = (const ObjectFactory&); FactoryMap _map; }; } // end of grs namespace 






A small example of using the factory
 #include "ObjectFactory.h" #include <iostream> enum Type { fooType, barType, maskedType, unknownType, firstType = fooType, lastType = maskedType, }; std::ostream & operator << (std::ostream & strm, const Type & type) { const char * names[] = {"foo", "bar", "masked"}; if (type < firstType || type > lastType) return strm << "unknown type(" << int(type) << ")"; return strm << names[type]; }; class Base { public: virtual ~Base() {} virtual Type type() const = 0; }; class Foo : public Base { public: virtual Type type() const { return fooType; } }; class Bar : public Foo { public: virtual Type type() const { return barType; } }; class MaskedFoo : public Foo { public: virtual Type type() const { return barType; } }; typedef grs::ObjectFactory<Base, Type> TypeFactory; void checkType(TypeFactory & factory, Type type) { std::auto_ptr<Base> p; p.reset(factory.create(type)); std::cout << "Object with type : " << type; if (p.get()) { if (type == p->type()) std::cout << " - successfully created\n"; else std::cout << " - created, but type mismatch\n"; } else std::cout << " - create failed\n"; } int main() { TypeFactory factory; factory.add<Foo>(fooType); factory.add<Bar>(barType); factory.add<MaskedFoo>(maskedType); checkType(factory, fooType); checkType(factory, barType); checkType(factory, maskedType); checkType(factory, unknownType); return 0; } 






Also, along with an example, the pattern can be taken from bitbucket , besides it there are several other useful classes.



That's all. Good and creative coding!



Links



If you are interested in design patterns, I advise you to pay attention to these books. In A.Aleksandresku's book, the features of C ++ syntax are very well conveyed when working with templates. And in the book of the gang of 4 you can find all the most popular patterns used in many programming languages.

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



All Articles