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:
parent
b7fa2062e0
commit
afc2e858a4
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue