implement factory pattern for products with condit

2019-06-24 06:14发布

问题:

I'd like to implement factory (or some other pattern) in a way that will allow me to compile the code without introducing 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 a part of my compilation/linking unit.

The way my codebase is setup, we may want to ship BMWCar only, or two or all three of them. So, I cannot make the create() dependent on the type.

How can I adapt the factory pattern for this ? Also, I'd like to avoid doing ifdefs since this is just a sample of my problem. The real codebase is huge and is not a practical solution to ifdef the code.

Update: Also, I am not allowed to use:

  • templates
  • has to conform to c++ 98 standard
  • cannot use boost

These are mostly due to customer build toolchain restrictions. I don't have a choice in changing these.

回答1:

Here is a complete example, C++98 style. I assumed that the list of possible car types is not known 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 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 main has started, i.e. in other static initialization you may be doing.

Note2: This (in fact most solutions) may not work on its own if the Car implementations are in their own shared object libraries (.so). The linker will simply not put a dependency on that .so into the complete binary, unless the binary needs a symbol from that .so. So you need special linker options to force the linker to do so. This is mostly a problem for distributions that make --as-needed the default (I'm looking at you, Ubuntu). Use --no-as-needed or -Wl,--no-as-needed to switch it back off, at least for the libraries containing car implementations.

There are similar problems with static libraries (.a). A .a file is simply 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 be forced to consider a symbol undefined with -u symbol_name. But that is the mangled symbol name, so it is kind of hard to guess. One symbol that would work for that purpose in my example is _ZN6BMWCar10registeredE, a.k.a BMW::registered in unmangled form. But it is probably better to define a function with C linkage so you don't need to guess the mangled variable name:

extern "C" void bmw_mark() { }

Then you don't have to guess the symbol name, and can just use -u bmw_mark. This has to be done in the same compilation unit as the other definitions for BMWCar, so they end up in the same .o file.



回答2:

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 initialize its own factory during the static initialization, using the static Init object.

The huge pain in this code is required to avoid order-of-initialization fiasco: a naive implementation would simply use a static map in CarFactory. Unfortunately, there is no guarantee that the BMWCar::initializeBmwCar constructor would run after the map in CarFactory. Sometimes with some compilers might work, sometimes it might just crash. So the idea is to use a static function (getMap) with a static variable (m), that is guaranteed to be initialized the first time getMap is called.

I know that clang/llvm uses this pattern to register the optimization pass.

Another solution that is more complicated, but much more flexible is to design a plugin system where each DLL implements one car type and exports one CreateCar function.

Then you can collect all these CreateCar during initialization by dynamically loading the library and calling GetProcAddress/dlsym.

Both solutions might be tricky to achieve on Windows, because (unless Car is abstract) the base Car implementation needs to go in its own library, and each plugin dll needs to link with that library.



回答3:

Instead of using a switch, you could use templates and specialization. The following example ships the implementation of 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();
}


回答4:

You could register the types dynamically to 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();
}


回答5:

My solution:

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 Car derived classes should register with CarFactory, the correct TypeCarCreator
  • statically call registerCreator for each Car derived class


回答6:

I'm not very good programmer in this thing. but, trying to give my best 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;
        }
    }
}