diff --git a/src/async/core/AsyncFactory.h b/src/async/core/AsyncFactory.h index c9298864..d81e5af1 100644 --- a/src/async/core/AsyncFactory.h +++ b/src/async/core/AsyncFactory.h @@ -1,8 +1,8 @@ /** -@file AsyncFactory.h -@brief Some templates used to support the creation of an object factory class -@author Tobias Blomberg / SM0SVX -@date 2014-01-26 +@file AsyncFactory.h +@brief Some templates used to support the creation of an object factory +@author Tobias Blomberg / SM0SVX +@date 2020-07-21 \verbatim SvxLink - A Multi Purpose Voice Services System for Ham Radio Use @@ -106,48 +106,78 @@ namespace Async /** @brief Base class for an object factory +@tparam T The baseclass +@tparam Args Contructor arguments, if any @author Tobias Blomberg / SM0SVX -@date 2014-01-26 +@date 2020-07-21 -Use this class as a base when creating an object factory. For example, to -create a factory for a class called "MyObj", use the code below. +This class implements the actual factory. The type of the factory class would +be something like Async::Factory for a base class representing animals. +It is most often adviced to create a typedef to represent the type of the +factory, e.g.: - struct MyObjFactoryBase : public FactoryBase +@code + typedef Async::Factory AnimalFactory; +@endcode + +Creating a typedef bocomes incresingly convenient when the constructor of the +objects being manufactured take one or more arguments since the type definition +will become quite long. + +Also the addition of a convenience function for creating objects make the usage +of the factory easier and more readable. This function make use of the typedef +declared above and also rely on that a convenience struct for the specific +factories have been declared as described for Async::SpecificFactory. + +@code + Animal* createAnimal(const std::string& obj_name) { - MyObjFactoryBase(const std::string &name) : FactoryBase(name) {} - }; + static AnimalSpecificFactory dog_factory; + static AnimalSpecificFactory cat_factory; + static AnimalSpecificFactory fox_factory; + return AnimalFactory::createNamedObject(obj_name); + } +@endcode +The following example will describe in more detail how to use the factory +classes. + +@example AsyncFactory_demo.cpp */ -template -class FactoryBase +template +class Factory { public: /** - * @brief Create an instance of the named class - * @param name The name of the class to create an instance of - * @return Returns a newly constucted object or 0 on failure + * @brief Create an instance of the named class + * @param name The name of the class to create an instance of + * @param args Arguments to pass to constructor, if any + * @return Returns a newly constucted object or 0 on failure */ - static T *createNamedObject(const std::string& name) + static T *createNamedObject(const std::string& name, Args... args) { - typename std::map*>::iterator it; - it = factories.find(name); - if (it == factories.end()) + typename std::map*>::iterator it; + it = factories().find(name); + if (it == factories().end()) { return 0; } - return (*it).second->createObject(); + return (*it).second->createObject(args...); } /* Factory::createNamedObject */ /** - * @brief Get a list of valid class names - * @return Return a string containing a list of valid class names + * @brief Get a list of valid class names + * @return Return a string containing a list of valid class names + * + * This function is mostly useful for printing a message informing about + * which classes are available. */ static std::string validFactories(void) { std::stringstream ss; - for (typename FactoryMap::const_iterator it = factories.begin(); - it != factories.end(); ++it) + for (typename FactoryMap::const_iterator it = factories().begin(); + it != factories().end(); ++it) { ss << "\"" << (*it).first << "\" "; } @@ -155,100 +185,133 @@ class FactoryBase } /** - * @brief Constructor + * @brief Constructor * @param name The name of the specific object factory being created */ - FactoryBase(const std::string &name) - : m_name(name) + Factory(const std::string &name) : m_name(name) { - typename FactoryMap::iterator it = factories.find(m_name); - assert(it == factories.end()); - factories[name] = this; + typename FactoryMap::iterator it = factories().find(m_name); + assert(it == factories().end()); + factories()[name] = this; } /* Factory::Factory */ /** - * @brief Destructor + * @brief Don't allow copy construction */ - virtual ~FactoryBase(void) + Factory(const Factory&) = delete; + + /** + * @brief Don't allow assignment + */ + Factory& operator=(const Factory&) = delete; + + /** + * @brief Destructor + */ + virtual ~Factory(void) { - typename FactoryMap::iterator it = factories.find(m_name); - assert(it != factories.end()); - factories.erase(it); + typename FactoryMap::iterator it = factories().find(m_name); + assert(it != factories().end()); + factories().erase(it); } /* Factory::~Factory */ protected: /** - * @brief Create and return a new instance of an object - * @return Returns a newly created instance of the object + * @brief Create and return a new instance of an object + * @param args Arguments to pass to constructor, if any + * @return Returns a newly created instance of the object */ - virtual T *createObject(void) = 0; + virtual T *createObject(Args... args) = 0; private: - typedef std::map*> FactoryMap; - static FactoryMap factories; + typedef std::map*> FactoryMap; std::string m_name; - FactoryBase(const FactoryBase&); - FactoryBase& operator=(const FactoryBase&); - -}; /* class FactoryBase */ - - -template -typename FactoryBase::FactoryMap FactoryBase::factories; + static FactoryMap& factories(void) + { + static FactoryMap factory_map; + return factory_map; + } +}; /* class Factory */ /** @brief Base class for a specific object factory +@tparam Base The baseclass +@tparam T The specific class +@tparam Args Contructor arguments, if any @author Tobias Blomberg / SM0SVX -@date 2014-01-26 +@date 2020-07-21 -This class should be used as the base for a specific factory. Let's say we -have a couple of classes MyObjOne and MyObjTwo which use MyObj as a base class. -Declare the following two classes to make it possible to create any of the two -objects using a factory. +This class is used as the base for a specific factory. To make it easier to +declare and use new specific factories it is beneficial to declare the +convenience class below. In this example we assume that there is a base class, +Animal, that is inherited by specific animal classes. - struct MyObjOneFactory : public MyObjFactory +@code + template + struct AnimalSpecificFactory : public Async::SpecificFactory { - MyObjOneFactory(void) : MyObjFactory("One") {} - }; - struct MyObjTwoFactory : public MyObjFactory - { - MyObjTwoFactory(void) : MyObjFactory("Two") {} + AnimalSpecificFactory(void) + : Async::SpecificFactory(T::OBJNAME) {} }; +@endcode -Now you need to instantiate one instance of each class so that they will -register in the factory. This have to be done before calling the -createNamedObject function. +The struct above relies on the fact that each specific animal class have +declared a constant, OBJNAME. That constant specify the name for the class +that is used when using the factory to create objects, i.e.: + +@code + struct Dog : public Animal + { + static constexpr const char* OBJNAME = "dog"; + // ... + }; +@endcode + +To make each specific class available to the factory an instance of each +specific factory will have to be created before calling the +Async::Factory::createNamedObject function, i.e.: + +@code + AnimalSpecificFactory dog_factory; +@endcode + +The instatiation of all specific factories is most easily placed in a function +that is declared in the same files as the base class, as described in the +documentation for the Async::Factory class. + +The following example will describe in more detail how to use the factory +classes. + +@example AsyncFactory_demo.cpp */ -template -class Factory : public FactoryT +template +class SpecificFactory : public Factory { public: /** - * @brief Constructor + * @brief Constructor * @param name The name of the specific object factory being created */ - Factory(const std::string &name) : FactoryT(name) {} + SpecificFactory(const std::string &name) : Factory(name) {} protected: /** - * @brief Create and return a new instance of an object - * @return Returns a newly created instance of the object + * @brief Create and return a new instance of an object + * @return Returns a newly created instance of the object */ - T *createObject(void) + T *createObject(Args... args) { - return new T; + return new T(args...); } }; -} /* namespace SvxLink */ +} /* namespace Async */ #endif /* ASYNC_FACTORY_INCLUDED */ - - /* * This file has not been truncated */ diff --git a/src/async/demo/AsyncFactory_demo.cpp b/src/async/demo/AsyncFactory_demo.cpp new file mode 100644 index 00000000..115f521f --- /dev/null +++ b/src/async/demo/AsyncFactory_demo.cpp @@ -0,0 +1,139 @@ +// +// This example demonstrates how to use the Async::Factory classes to enable a +// class being created using the factory pattern. +// +// The scenario is that we have a base class, Animal, which specific animal +// classes derive from, e.g. Dog, Cat etc. Each animal have a given name which +// is stored in the base class. That name is given to the constructor of each +// animal which demonstrates how to use the Async::Factory class with +// constructors that take one or more arguments. This is made possible by the +// fact that the Async::Factory classes use variadic templates. +// +// The base class also have a pure virtual function, "say", that each derived +// class must implement. It should return a string describing what the specific +// animal say. +// +// Just before main() a number of convenience constructs; a typedef, a +// templated struct and a function, have been added to make using the +// Async::Factory easier. These would normally go into the same files as where +// the base class is declared. +// + +#include +#include +#include + + + // The base class for all animals. This class has no connection to the + // Async::Factory classes. +class Animal +{ + public: + Animal(const std::string& given_name) : m_given_name(given_name) {} + virtual ~Animal(void) {} + const std::string& givenName(void) const { return m_given_name; } + virtual const char* say(void) const = 0; + private: + std::string m_given_name; +}; + + + // A class representing a dog. The OBJNAME constant is needed to make the + // convenience classas defined below work. +struct Dog : public Animal +{ + static constexpr const char* OBJNAME = "dog"; + Dog(const std::string& given_name) : Animal(given_name) {} + virtual const char* say(void) const { return "Voff"; } +}; + + + // A class representing a cat. The OBJNAME constant is needed to make the + // convenience classas defined below work. +struct Cat : public Animal +{ + static constexpr const char* OBJNAME = "cat"; + Cat(const std::string& given_name) : Animal(given_name) {} + virtual const char* say(void) const { return "Meow"; } +}; + + + // A class representing a fox. The OBJNAME constant is needed to make the + // convenience classas defined below work. +struct Fox : public Animal +{ + static constexpr const char* OBJNAME = "fox"; + Fox(const std::string& given_name) : Animal(given_name) {} + virtual const char* say(void) const + { + return "Ding, ding, ding, ding, ding, di-ding, di-ding"; + } +}; + + + // Add a custom animal, in this case a cow. This animal is not part of the + // "core animals" but instead it is added just before calling createAnimal. +struct Cow : public Animal +{ + static constexpr const char* OBJNAME = "cow"; + Cow(const std::string& given_name) : Animal(given_name) {} + virtual const char* say(void) const + { + return "Moooo"; + } +}; + + + // A convenience struct to make instantiation of spcecific animals easier. + // This class relies on the fact that each specific animal class have + // declared a constant, OBJNAME. That constant specify the name for the class + // that is used when using the factory to create objects. +template +struct AnimalSpecificFactory + : public Async::SpecificFactory +{ + AnimalSpecificFactory(void) + : Async::SpecificFactory(T::OBJNAME) {} +}; + + // A convenience typedef to make access to Async::Factory members easier. +typedef Async::Factory AnimalFactory; + + // A function for creating an animal +Animal* createAnimal(const std::string& obj_name, + const std::string& animal_name) +{ + static AnimalSpecificFactory dog_factory; + static AnimalSpecificFactory cat_factory; + static AnimalSpecificFactory fox_factory; + return AnimalFactory::createNamedObject(obj_name, animal_name); +} + + +int main(int argc, const char *argv[]) +{ + if (argc < 3) + { + std::cout << "Usage: " << argv[0] << " " + << std::endl; + exit(1); + } + const std::string obj_name(argv[1]); + const std::string given_name(argv[2]); + + // Add our custom animal before calling createAnimal + AnimalSpecificFactory cow_factory; + + // Create the animal specified on the command line + Animal* obj = createAnimal(obj_name, given_name); + if (obj == nullptr) + { + std::cout << "Sorry, but there is no animal \"" << argv[1] << "\" :-(" + << std::endl; + std::cout << "Valid animals: " << AnimalFactory::validFactories() + << std::endl; + exit(1); + } + std::cout << "- Hello, " << obj->givenName() << "! What do you say?\n- " + << obj->say() << "!" << std::endl; +} diff --git a/src/async/demo/CMakeLists.txt b/src/async/demo/CMakeLists.txt index 9a5ba4c1..303663ff 100644 --- a/src/async/demo/CMakeLists.txt +++ b/src/async/demo/CMakeLists.txt @@ -4,7 +4,7 @@ set(CPPPROGS AsyncAudioIO_demo AsyncDnsLookup_demo AsyncFdWatch_demo AsyncSerial_demo AsyncAtTimer_demo AsyncExec_demo AsyncPtyStreamBuf_demo AsyncMsg_demo AsyncFramedTcpServer_demo AsyncFramedTcpClient_demo AsyncAudioSelector_demo - AsyncAudioFsf_demo AsyncHttpServer_demo) + AsyncAudioFsf_demo AsyncHttpServer_demo AsyncFactory_demo) foreach(prog ${CPPPROGS}) diff --git a/src/versions b/src/versions index 046d0cd0..1cde0ec3 100644 --- a/src/versions +++ b/src/versions @@ -8,7 +8,7 @@ QTEL=1.2.4 LIBECHOLIB=1.3.3 # Version for the Async library -LIBASYNC=1.6.0.99.7 +LIBASYNC=1.6.0.99.8 # SvxLink versions SVXLINK=1.7.99.22