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.

+3


source to share


6 answers


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 &registry();
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 &reg = 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 &reg = 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.

+1


source


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.

+2


source


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();
}

      

0


source


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();
}

      

0


source


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
0


source


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;
        }
    }
}

      

0


source







All Articles