为有条件编译的产品实现工厂模式

implement factory pattern for products with conditional compiling

我想以一种允许我在不引入类型依赖性的情况下编译代码的方式实现工厂(或其他模式)。

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

当我编译 CarFactory 时,我需要将 BMWCar、PorscheCar 和 MercCar 作为我的 compilation/linking 单元的一部分。

根据我的代码库的设置方式,我们可能只想发布 BMWCar,或者其中的两个或全部三个。所以,我不能使 create() 依赖于类型。

我该如何调整工厂模式呢?另外,我想避免执行 ifdefs,因为这只是我的问题的一个示例。真正的代码库是巨大的,不是 ifdef 代码的实用解决方案。

更新: 另外,我不允许使用:

这主要是由于客户构建工具链的限制。我没有选择改变这些。

您可以使用模板和专业化来代替 switch。 以下示例包含 BMWCarMercCar 的实现,但不包括 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();
}

您可以将类型动态注册到数组。我想到的第一个解决方案类似于(您可能想要更好的设计):

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

我通常会做类似这样的事情:

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

并且在每个 class 实施中:

 class BMWCar : public Car
 { 
     struct Init
     {
         Init() 
         {
             CarFactory::RegisterCar(BMW, [](){return new BMWCar(); });
         }
     };
     static Init initializeBmwCar;
     /** .. */ 
 };

 /*** BMWCar.cpp ***/
 BMWCar::Init BMWCar::initializeBmwCar;

这是可行的,因为每个类型在静态初始化期间使用 static Init 对象初始化自己的工厂。

此代码中的巨大痛苦是为了避免初始化顺序的失败:天真的实现会简单地在 CarFactory 中使用静态映射。不幸的是,无法保证 BMWCar::initializeBmwCar 构造函数会 运行 映射 CarFactory 之后。有时某些编译器可能会工作,有时它可能会崩溃。 所以这个想法是使用一个静态函数(getMap)和一个静态变量(m),保证在第一次调用getMap时被初始化。

我知道 clang/llvm 使用此模式来注册优化过程。

另一个更复杂但更灵活的解决方案是设计一个插件系统,其中每个 DLL 实现一个 car 类型并导出一个 CreateCar 函数。

然后你可以通过动态加载库并调用GetProcAddress/dlsym在初始化期间收集所有这些CreateCar

这两种解决方案都可能难以在 Windows 上实现,因为(除非 Car 是抽象的)基础 Car 实现需要放在它自己的库中,并且每个插件 dll需要 link 使用该库。

我的解决方案:

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

我还需要弄清楚:

  • 所有 Car derived classes 应该注册 CarFactory,正确的 TypeCarCreator
  • 为每个派生的 Car 静态调用 registerCreator class

这是一个完整的例子,C++98风格。我假设在编译时不知道可能的汽车类型列表,所以我将枚举更改为字符串。

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 和 merc.cc 相似但未显示):

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

编译和运行:

-*- 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

注意 1: 在 main 启动之前,您不能假设所有 类 都已注册,即在您可能正在执行的其他静态初始化中。

注意 2: 如果 Car 实现在它们自己的共享对象库 (.so) 中,则此(实际上大多数解决方案)可能无法单独工作。链接器不会将对那个 .so 的依赖放入完整的二进制文件中,除非二进制文件需要来自那个 .so 的符号。所以你需要特殊的链接器选项来强制链接器这样做。对于将 --as-needed 作为默认值的发行版来说,这主要是一个问题(我在看着你,Ubuntu)。使用 --no-as-needed-Wl,--no-as-needed 将其关闭,至少对于包含汽车实现的库。

静态库(.a)也有类似的问题。一个 .a 文件只是几个 .o 文件的集合,链接器将只包含 .a 文件中那些包含以前未定义的符号的 .o 文件。可以强制链接器考虑使用 -u symbol_name 未定义的符号。但那是被破坏的符号名称,所以有点难以猜测。在我的示例中,可用于该目的的一个符号是 _ZN6BMWCar10registeredE、a.k.a BMW::registered 的未损坏形式。但是最好用 C 链接定义一个函数,这样你就不需要猜测损坏的变量名:

extern "C" void bmw_mark() { }

那就不用猜符号名了,直接用-u bmw_mark就可以了。 这必须在与 BMWCar 的其他定义相同的编译单元中完成,因此它们最终会出现在同一个 .o 文件中。

我在这方面不是很好的程序员。但是,尝试给出我的最佳答案。

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