Implement a factory pattern for conditionally compiled products
I would like to implement a factory (or some other pattern) in such a way that I can compile the code without introducing a type dependency.
enum CarType
{
BMW,
PORSCHE,
MERC
};
class CarFactory
{
public:
static Car* create(CarType type)
{
switch(type)
{
case BMW : return new BMWCar();
case PORSCHE : return new PorscheCar();
default : return new MercCar();
}
}
};
When I compile CarFactory I need to include BMWCar, PorscheCar and MercCar as part of my compile / link module.
As my codebase is set up, we can only send a BMWCar, or two or all three of them. This way I cannot make it create()
type dependent.
How can I adapt the factory pattern for this? Also, I would like to avoid doing ifdefs as this is just a sample of my problem. The real codebase is huge and not a practical solution for ifdef code.
Update: Also, I am not allowed to use:
- Templates
- must conform to C ++ 98 standard
- can't use boost
This is mainly due to the limitations of client binding. I have no choice in changing them.
source to share
Here is a complete example, C ++ 98 style. I assumed the list of possible car types is unknown at compile time, so I changed the enum to a string.
cartype.hh:
#include <map>
#include <string>
#include <vector>
struct Car {
virtual std::string type() = 0;
virtual ~Car() {}
};
// Factory
class CarFactory
{
typedef std::map<std::string, Car *(*)()> Registry;
static Registry ®istry();
public:
static std::vector<std::string> getRegisteredTypes();
static void registerCarType(std::string type, Car *(*creator)());
static Car* create(std::string type);
};
cartype.cc:
#include <map>
#include <string>
#include <vector>
#include "cartype.hh"
// Factory
CarFactory::Registry &CarFactory::registry()
{
static std::map<std::string, Car *(*)()> r;
return r;
}
std::vector<std::string> CarFactory::getRegisteredTypes()
{
static const Registry ® = registry();
std::vector<std::string> types;
types.reserve(reg.size());
Registry::const_iterator end = reg.end();
for(Registry::const_iterator it = reg.begin(); it != end; ++it)
types.push_back(it->first);
return types;
}
void CarFactory::registerCarType(std::string type, Car *(*creator)())
{
registry()[type] = creator;
}
Car* CarFactory::create(std::string type)
{
static const Registry ® = registry();
Registry::const_iterator result = reg.find(type);
if(result != reg.end())
return result->second();
throw "Unregistered car type";
}
bmw.cc (porsche.cc and merc.cc are similar but not shown):
#include <string>
#include "cartype.hh"
// BMW
class BMWCar : public Car
{
static const bool registered;
static Car *create() { return new BMWCar; }
public:
virtual std::string type() { return "BMW"; }
};
const bool BMWCar::registered =
(CarFactory::registerCarType("BMW", BMWCar::create),
true);
check.cc:
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include "cartype.hh"
int main()
{
// all car types should be registered when we enter main
std::vector<std::string> types = CarFactory::getRegisteredTypes();
for(std::size_t i = 0; i < types.size(); ++i)
{
std::auto_ptr<Car> car(CarFactory::create(types[i]));
std::cout << "Wanted: " << types[i] << ", Got: " << car->type() << std::endl;
}
}
Compiling and running:
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Tue Aug 4 01:24:51
set -ex; g++ -std=c++98 -g -O3 -Wall check.cc cartype.cc bmw.cc porsche.cc -o check; ./check
+ g++ -std=c++98 -g -O3 -Wall check.cc cartype.cc bmw.cc porsche.cc -o check
+ ./check
Wanted: BMW, Got: BMW
Wanted: PORSCHE, Got: Porsche
Compilation finished at Tue Aug 4 01:24:54
Note1: you cannot assume that all classes are registered until after main starts, i.e. in another static initialization that you can do.
Note2: This (in fact most solutions) may not work on its own if the implementations Car
are in their shared object libraries (.so). The compiler will simply not depend on that .so in a complete binary unless the binary needs a symbol from that .so. Therefore, you need linker special capabilities to force the linker to do this. This is mostly a problem for distributions that make --as-needed
it the default (I'm looking at you, Ubuntu). Use --no-as-needed
or -Wl,--no-as-needed
to disable it, at least for libraries containing car versions.
Similar problems arise with static libraries (.a). A .a file is just a collection of several .o files, and the linker will only include those .o files from the .a file that contain symbols that were previously undefined. The linker can enforce the undefined character with -u symbol_name
. But this is the desired name for the symbol, so it is difficult to guess. One character that will work for this purpose in my example is _ZN6BMWCar10registeredE
, aka BMW::registered
in unbuilt form. But it's probably better to define a C-linked function, so you don't have to guess the name of the changed variable:
extern "C" void bmw_mark() { }
Then you don't have to guess the symbol name and just use -u bmw_mark
. This needs to be done in the same compilation unit as the other definitions for BMWCar
, so they end up in the same .o file.
source to share
I usually do something similar to this:
class CarFactory
{
public:
static void RegisterCar(CarType t, std::function<Car*()> f)
{
getMap().emplace(t, f);
}
static Car* create(CarType type)
{
return getMap().at(type)();
}
private:
static std::unorderd_map<CarType, std::function<Car*()> >& getMap()
{
static std::unorderd_map<CarType, std::function<Car*()> > m;
return m;
}
};
And in each class implementation:
class BMWCar : public Car
{
struct Init
{
Init()
{
CarFactory::RegisterCar(BMW, [](){return new BMWCar(); });
}
};
static Init initializeBmwCar;
/** .. */
};
/*** BMWCar.cpp ***/
BMWCar::Init BMWCar::initializeBmwCar;
This works because each type initializes its own factory during static initialization using an object static Init
.
A huge pain in this code is required to avoid the initialization fiasco: the naive implementation just used a static map in CarFactory
. Unfortunately, there is no guarantee that the constructor BMWCar::initializeBmwCar
will work after the map in CarFactory
. Sometimes it can work with some compilers, sometimes it just crashes. So the idea is to use a static function ( getMap
) with a static variable ( m
) that is guaranteed to be initialized on the first call getMap
.
I know clang
/ llvm
uses this pattern to log an optimization pass.
Another more complex solution, but much more flexible, is to create a plugin system in which each DLL implements one type car
and exports one function CreateCar
.
Then you can collect all of these CreateCar
during initialization by dynamically loading the library and calling GetProcAddress
/ dlsym
.
Both solutions can be tricky to achieve on Windows because (if car
not abstract), the base implementation car
must go in its own library, and each dll plugin must link against that library.
source to share
Instead of using, switch
you can use templates and specialization. The following example implements both BMWCar
and MercCar
but excludes PorscheCar
:
enum CarType
{
BMW,
PORSCHE,
MERC
};
struct Car {};
struct BMWCar:public Car{};
// DO NOT SHIP
// struct PorscheCar:public Car{};
struct MercCar:public Car{};
template <CarType type>
struct CarFactory;
template <>
struct CarFactory<BMW>
{
static Car* create()
{
return new BMWCar();
}
};
/*
// DO NOT SHIP
template <>
struct CarFactory<PORSCHE>
{
static Car* create()
{
return new PorscheCar();
}
};
*/
template <>
struct CarFactory<MERC>
{
static Car* create()
{
return new MercCar();
}
};
int main()
{
Car* m = CarFactory<MERC>::create();
}
source to share
You can dynamically register types in an array. The first solution that comes to my mind would be something like (you probably want a better design):
class CarTypeRegister {
protected:
CarTypeRegister(enum CarType type) {
types[type] = this; /* -Creating a static variable from child class will register the type to the factory */
}
virtual ~CarTyperegister() {
}
public:
static CarTypeRegister *types[END_OF_CARTYPE];
virtual Car *construct() = 0;
};
CarTypeRegister *CarTypeRegister::types = {nullptr};
Car * CarFactory::create(CarType type)
{
if (!CarTypesRegister::types[type])
return nullptr;
return CarTypesRegister::types[type]->construct();
}
source to share
My decision:
class CarCreator
{
public:
virtual Car* operator(int otherArgs) = 0;
};
class BMWCarCreator : pure CarCreator
{
public:
Car* operator(int otherArgs) { return new BMWCar(otherArgs); }
};
// in BMWCar.cpp
class BMWCar : public Car
{
// SOME WAY TO STATICALLY REGISTER BMWCarCreator to BMWCarType
BMWCar( int otherArgs ) { }
};
class CarFactory
{
public:
// associates the type-creator_FnObj
void registerCreator(CarType type, CarCreator* creator);
// Creates the car based on associated CarCreator*
Car* create(CarType type, int otherArgs)
{
CarCreator* creator = this->findCreatorAssociation(type);
if (!creator)
throw exception;
return creator(otherArgs);
}
}
I still need to figure out:
- All classes created by car must register in CarFactory with the correct TypeCarCreator
- a static call to registerCreator for each derived Car class
source to share
I'm not a very good programmer at this thing. but trying to give a better answer.
#include <iostream>
using namespace std;
class Car { virtual void SomeMethods() {} /* Your Code */ };
class BMWCar : public Car { /* Your Code */ };
class PorscheCar : public Car { /* Your Code */ };
class MercCar : public Car { /* Your Code */ };
enum CarType
{
BMW,
PORSCHE,
MERC,
BMW_N_PORSCHE,
BMW_N_MERC,
PORSCHE_N_MERC,
ALL
};
class FinalCar
{
private:
Car* m_car[3];
size_t m_size;
CarType m_CarType;
public:
FinalCar(Car* car1, CarType carType)
{
m_car[0] = car1;
m_car[1] = nullptr;
m_car[2] = nullptr;
m_size = 1;
m_CarType = carType;
}
FinalCar(Car* car1, Car* car2, CarType carType)
{
m_car[0] = car1;
m_car[1] = car2;
m_car[2] = nullptr;
m_size = 2;
m_CarType = carType;
}
FinalCar(Car* car1, Car* car2, Car* car3, CarType carType)
{
m_car[0] = car1;
m_car[1] = car2;
m_car[2] = car3;
m_size = 3;
m_CarType = carType;
}
size_t GetSize()
{
return m_size;
}
CarType GetCarType()
{
return m_CarType;
}
Car* GetCar(size_t n)
{
return m_car[n];
}
~FinalCar()
{
if (m_car[0] != nullptr)
{
delete m_car[0];
m_car[0] = nullptr;
}
if (m_car[1] != nullptr)
{
delete m_car[1];
m_car[1] = nullptr;
}
if (m_car[2] != nullptr)
{
delete m_car[2];
m_car[2] = nullptr;
}
}
};
class CarFactory
{
public:
static FinalCar create(CarType type)
{
switch (type)
{
case BMW:
return FinalCar(new BMWCar(), BMW);
break;
case PORSCHE:
return FinalCar(new PorscheCar(), PORSCHE);
break;
case MERC:
return FinalCar(new MercCar(), MERC);
break;
case BMW_N_PORSCHE:
return FinalCar(new BMWCar(), new PorscheCar(), BMW_N_PORSCHE);
break;
case BMW_N_MERC:
return FinalCar(new BMWCar(), new MercCar(), BMW_N_MERC);
break;
case PORSCHE_N_MERC:
return FinalCar(new PorscheCar(), new MercCar(), PORSCHE_N_MERC);
break;
default:
return FinalCar(new BMWCar(), new PorscheCar(), new MercCar(), ALL);
break;
}
}
};
int main()
{
FinalCar myCar = CarFactory::create(PORSCHE_N_MERC);
for (int i = 0; i < myCar.GetSize(); i++)
{
Car* tmpCar = myCar.GetCar(i);
if (dynamic_cast<BMWCar*>(tmpCar))
{
cout << "BMWCar*" << endl;
}
else if (dynamic_cast<PorscheCar*>(tmpCar))
{
cout << "PorscheCar*" << endl;
}
else if (dynamic_cast<MercCar*>(tmpCar))
{
cout << "MercCar*" << endl;
}
}
}
source to share