简化配置文件驱动的工厂

时间:2009-08-12 20:40:42

标签: c++

HeJ小鼠!

小心这是一个很长的帖子 - 如果你很容易生气,最好跳过它。 ; - )

我目前正在研究的项目是读取不同传感器的电压测量值并计算相应的物理值。有许多通道,每个通道可以连接不同的传感器。基本上,传感器的类型是已知的,但灵敏度和偏差等细节可能有很大差异。虽然每种类型的传感器都有一个参数化类,但参数是从配置文件中提取的。

假设有以下类层次结构:

class Sensor
{
public:
  virtual double Calculate(const double &arg)=0;
};
class PTCResistor
{
  PTCResistor(XMLNode &node);
  double Calculate(const double &arg);
};
class Thermocouple
{
  Thermocouple(XMLNode &node);
  double Calculate(const double &arg);
};

主配置文件如下所示:

<channels>
  <TurbineInletTemp sensor="Thermocouple.TypeK.xml" />
  <CylinderHeadTemp sensor="PTCResistor.PT500.xml" />
  ...
</channels>

如您所见,每个频道标签都有一个属性传感器,指定一些符合参数信息和传感器种类的欧姆XML文件。它看起来像这样:

<sensor class="PTCResistor">
  <param Rref="500" Tref="0">
  ...
</sensor>

这些参考XML文件的格式可能有所不同,但每个派生类的构造函数都能理解这些格式。

在程序启动时,解析主配置文件,工厂类解析指向包含传感器特定信息的XML的链接,检查类标记并调用相应的构造函数。像这样:

string chnTITFile = rootNode.GetNode("TurbineInletTemp").GetAttribute("sensor");
XMLNode chnTITNode = XMLNode.parseFile(RootPath + chnTITFile,"sensor")
string className = chnTITNode .GetAttribute("class");

if(className == "Thermocouple") {
  Sensor* sensorTIT = new Thermocouple(chnTITNode);
}
else if(className = "PTCResistor") {
  Sensor* sensorTIT = new PTCResistor(chnTITNode);
}

这是我提出的最精简的解决方案,因为添加新的派生类时只有很少的地方可以改变。基本上,一个人编写一个新的派生类,并在工厂中添加另一个if-branch。不幸的是,这涉及很多字符串比较。由于类和通道的数量可能相当大(并且目标系统是低功率的),我担心。

另一种可能性是散列类名。这可能看起来像这样:

string chnTITFile = rootNode.GetNode("TurbineInletTemp).GetAttribute("sensor");
XMLNode chnTITNode = XMLNode.parseFile(RootPath + chnTITFile,"sensor")
string className = chnTITNode .GetAttribute("class");  

switch(hash(className)) {
case ThermocoupleHash: ...
case PTCResistorHash: ...
...
}

问题在于:需要维护包含哈希值的枚举。

在编写此代码时,我觉得添加新传感器的过程相当繁琐。任何想法如何减少混乱?或者这已经是最不可能得到的邪恶了?

感谢阅读! ·阿尔

EDIT1

根据Neil Butterworth的建议,我考虑通过类似

的函数扩展每个类
  static Sensor* Thermocouple::Create(XMLNode &node) {
     return new Thermocouple(node);     
  }

并创建一个哈希表,将类名字符串与指向此静态函数的函数指针相关联:

typedef Sensor* (*CreateFunct)(XMLNode &node);

class SensorFactory 
{
public:
  SensorFactory() {
    classNameMap["Thermocouple"] = Thermocouple::Create;
    classNameMap["PTCResistor"] = PTCResistor::Create;
  };

  Sensor* ChannelByName(string chnName) {
    string chnFile= rootNode.GetNode(chnName).GetAttribute("sensor");
    XMLNode chnSensorNode = XMLNode.parseFile(RootPath + chnFile,"sensor")
    string className = chnSensorNode.GetAttribute("class");
    map<string, CreateFunct>::iterator iterat = classNameMap.find(className);
    if(iterat != classNameMap.end()) {
       CreateFunct f = iterat->second;
       return f(chnTITNode);
    }
  };

private:
  map<string, CreateFunct> classNameMap;
}

这允许为通过名称标识的通道创建匹配的传感器对象。查找是通过哈希比较完成的,只需要在工厂类的构造函数中维护映射初始化。

2 个答案:

答案 0 :(得分:3)

这是工厂模式 - 有一篇维基百科文章here。实现这一目标的方式很多,但我经常使用的是:

  • 创建对象层次结构 - 每个类都有一个静态Create function,可以从中创建类的实例 参数 - 参数必须对层次结构中的所有Create函数都是通用的
  • 为该类创建一个类名称的地图
  • 每次添加新类时都会注册类名字符串 和带地图的创建功能
  • 解析XML时,获取类名,然后使用它来查看 该类的创建功能
  • 调用Create函数创建所需类的对象

Create函数可以有效地将描述该类的XML实体的根作为其参数之一。然后,他们可以进一步解析XML 获取特定对象构造所需的值。

答案 1 :(得分:1)

一种模式是让您的派生类在静态初始化期间向工厂注册,以便您建立一个包含所有派生类的列表。解析XML文件时,在数据结构中查找字符串以确定要使用的正确派生实现。