A factory for dynamically creating classes derived from a "prototype" by name.
- Factory: A object that has a look-up table between class names and pointers to functions that can create them
- Maker: A function that can create a specific class
- Prototype: A abstract base class from which derived classes can be used
The factory itself works on two steps.
First, all of the different derived classes "register" or "declare" themselves so that the factory knows how to create them. This registration is done by providing a function that can construct them in association with the name of the derived class.
Second, the factory creates any of the registered classes and return a pointer to it in the form of a prototype-class pointer.
Using the factory effectively can be done in situations where many classes all follow the same design structure but have different implementations for specific steps. In order to reflect this "same design structure", we define an abstract base class (a class with at least one pure virtual function) for all of our derived classes to inherit from. This abstract base class is our "prototype".
// LibraryEntry.hpp
#ifndef LIBRARYENTRY_HPP
#define LIBRARYENTRY_HPP
// we need the factory template
#include "Factory/Factory.hpp"
// this class is our prototype
class LibraryEntry {
public:
// virtual destructor so we can dynamically create derived classes
virtual ~LibraryEntry() = default;
// pure virtual function that our derived classes will implement
virtual std::string name() = 0;
// the factory type that we will use here
using Factory = ::factory::Factory<LibraryEntry>;
}; // LibraryEntry
// a macro to help with registering our library entries with our factory
#define DECLARE_LIBRARYENTRY(CLASS) \
namespace { \
auto v = ::LibraryEntry::Factory::get().declare<CLASS>(#CLASS) \
}
#endif // LIBRARYENTRY_HPP
This LibraryEntry
prototype satisfies our requirements. Now, we can define several other library entries in other source files.
// Book.cpp
#include "LibraryEntry.hpp"
namespace library {
class Book : public LibraryEntry {
public :
virtual std::string name() final override {
return "Where the Red Fern Grows";
}
};
}
DECLARE_LIBRARYENTRY(library::Book)
// Podcast.cpp
#include "LibraryEntry.hpp"
namespace library {
namespace audio {
class Podcast : public LibraryEntry {
public :
virtual std::string name() final override {
return "538 Politics Podcast";
}
};
}
}
DECLARE_LIBRARYENTRY(library::audio::Podcast)
// Album.cpp
#include "LibraryEntry.hpp"
namespace library {
namespace audio {
class Album : public LibraryEntry {
public :
virtual std::string name() final override {
return "Kind of Blue";
}
};
}
}
DECLARE_LIBRARYENTRY(library::audio::Album)
Since we use an anonymous namespace to declare the various library entries,
the dummy variables that we are defining are static
and therefore will be
constructed during library load. Since they are constructed from the output
of the factory declaration function, we are able to register our before the main
program even begins.
#include "LibraryEntry.hpp"
int main(int argc, char* argv[]) {
std::string full_cpp_name{argv[1]};
auto entry_ptr{LibraryEntry::Factory::get().make(full_cpp_name)};
std::cout << entry_ptr->name() << std::endl;
}
Compiling this main into the favorite-things
executable would then lead to the behavior.
$ favorite-things library::Book
Where the Red Fern Grows
$ favorite-things library::audio::Podcast
538 Politics Podcast
$ favorite-things library::audio::Album
Kind of Blue