Improve usability for the Async::Factory class

- Introduce variadic templates to support classes with constructors that
  take one or more arguments.
- Better class naming
- Better documentation
- Add a detaild example also showing usage of constructor arguments.
This commit is contained in:
Tobias Blomberg 2020-07-21 14:38:55 +02:00
parent b7fa2062e0
commit afc2e858a4
4 changed files with 277 additions and 75 deletions

View File

@ -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<Animal> 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<MyObj>
@code
typedef Async::Factory<Animal> 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<MyObj>(name) {}
};
static AnimalSpecificFactory<Dog> dog_factory;
static AnimalSpecificFactory<Cat> cat_factory;
static AnimalSpecificFactory<Fox> 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 T>
class FactoryBase
template <class T, typename... Args>
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<std::string, FactoryBase<T>*>::iterator it;
it = factories.find(name);
if (it == factories.end())
typename std::map<std::string, Factory<T, Args...>*>::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<T, Args...>&) = delete;
/**
* @brief Don't allow assignment
*/
Factory& operator=(const Factory<T, Args...>&) = 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<std::string, FactoryBase<T>*> FactoryMap;
static FactoryMap factories;
typedef std::map<std::string, Factory<T, Args...>*> FactoryMap;
std::string m_name;
FactoryBase(const FactoryBase<T>&);
FactoryBase& operator=(const FactoryBase<T>&);
}; /* class FactoryBase */
template <class T>
typename FactoryBase<T>::FactoryMap FactoryBase<T>::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<MyObjOne>
@code
template <class T>
struct AnimalSpecificFactory : public Async::SpecificFactory<Animal, T>
{
MyObjOneFactory(void) : MyObjFactory<MyObjOne>("One") {}
};
struct MyObjTwoFactory : public MyObjFactory<MyObjTwo>
{
MyObjTwoFactory(void) : MyObjFactory<MyObjTwo>("Two") {}
AnimalSpecificFactory(void)
: Async::SpecificFactory<Animal, T>(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> 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 FactoryT, class T>
class Factory : public FactoryT
template <class Base, class T, typename... Args>
class SpecificFactory : public Factory<Base, Args...>
{
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<Base, Args...>(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
*/

View File

@ -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 <iostream>
#include <cassert>
#include <AsyncFactory.h>
// 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 <class T>
struct AnimalSpecificFactory
: public Async::SpecificFactory<Animal, T, std::string>
{
AnimalSpecificFactory(void)
: Async::SpecificFactory<Animal, T, std::string>(T::OBJNAME) {}
};
// A convenience typedef to make access to Async::Factory members easier.
typedef Async::Factory<Animal, std::string> AnimalFactory;
// A function for creating an animal
Animal* createAnimal(const std::string& obj_name,
const std::string& animal_name)
{
static AnimalSpecificFactory<Dog> dog_factory;
static AnimalSpecificFactory<Cat> cat_factory;
static AnimalSpecificFactory<Fox> fox_factory;
return AnimalFactory::createNamedObject(obj_name, animal_name);
}
int main(int argc, const char *argv[])
{
if (argc < 3)
{
std::cout << "Usage: " << argv[0] << " <obj name> <given name>"
<< 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> 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;
}

View File

@ -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})

View File

@ -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