tags:

views:

85

answers:

2

Hej!

Caution this is a long post -- if you are easily annoyed better skip it. ;-)

The Project I am working on currently is about reading voltage measurements of different sensors and calculating the respective physical value. There are a number of channels each of which can have a different sensor attached. Basicly the kind of sensor is known but the details like sensitivity and bias can vary widely. While there is a parametrized class for each type of sensor, the parameters are pulled from config files.

Suppose there is the following class hierachy:

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

The main config file looks like this:

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

As you see each channel tag has a attribute sensor specifying some ohter XML-File which contians parameter information and the kind of sensor. It looks something like this:

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

The format of these reference XML-files can vary but is is understood by the constructor of each derived class.

Upon program start the main config file is parsed and a factory class resolves the link to the XMLs containing the sensor specific information, examines the class tag and calls the respective constructors. Like this:

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

This is the leanest solution I came up with in the sense that there are only few places to alter when a new derived class is added. Basicly one writes a new derived class and the adds another if-branch in the factory. Unfortunately this involves a lot of string comparisons. Since the number of classes and channels can be rather large (and the target system is low-powered) I am concerned.

Another possibility would be hash the class names. This would probably look like this:

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: ...
...
}

Problem here: need to maintain the enum containing the hash-values.

As I am writing this code I feel that the process of adding a new sensor can be rather tedious. Any ideas how to reduce the mess? Or is this already the least of evil one can possibly get?

Thanks for reading! Arne

EDIT1

As suggested by Neil Butterworth I consider to extend each class by a function like

  static Sensor* Thermocouple::Create(XMLNode &node) {
     return new Thermocouple(node);     
  }

and create a hash table that relates the class name string to a function pointer to this static function:

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

This allows to create the matching sensor object for a channel identified by name. The lookup is accomplished by hash-comparision and one only needs to maintain map initialization in the constructor of the factory class.

+2  A: 

This is the Factory pattern - there's a Wikipedia article here. Thre are a zillion ways of implementing this, but one that I often use is:

  • create an object hierarchy - each class has a static Create function, which can create an instance of the class from its parameters - the parameters must be common to all Create functions in the hierarchy
  • create a map of class name to Create function for that class
  • every time you add a new class register the class name string and the Create function with the map
  • when parsing the XML,get the class name and then use it to look up the Create function for that class
  • call the Create function to create an object of the required class

The Create functions can usefully take the root of the XML entity describing the class as one of its parameters. They can then further parse the XML to get values needed for specific object construction.

anon
Thanks for your comment. I have read the article. Shure, the above code might not be 'exactly' an factory since the constructors are public, but it's really not what I am after with the question. I am looking for a way to reduce the hassle of adding new derived classes while beeing as performant as possible in the factory.
Arne
It's really easy to add a new class. In thed code I'm working on at the moment it's one line of code to register the class and perhaps 10 lines of code to write the Create function. And if you are parsing XML, performance is not an issue.
anon
So adding a static function in each derived class and create a map that relates a string to a function-pointer to this static function?
Arne
That's the very basic idea, yes.
anon
could you elaborate on the term 'basic' ? What yould be a not-so-basic solution?
Arne
I've edited my answer to hopefully make it clearer. The map of string to static function will work well in most cases, and I would get this working first - it should be wrapped in a Factory class. It's handy to create a Register class using templates which allows you to register classes and names with the Factory, but this really isn't necessary for the first iteration. You can later also consider registering not function pointers but Creator objects, which can be much more flexible
anon
I think I get the point about the creator objects but I have absolutely no idea how templates would help here..
Arne
also, the Factory instance can usefully be a Singleton, in which case registration becomes even easier and can effectively be performed statically.
anon
Re templates - this is how my current code registers a new data source type statically: static RegisterDS <DSDateSeq> regrs1_( DATESEQ_TAG ); The template is parameterised on type and the constructor provides the name. But I can't very well describe a flexible factory architecture in SO comments, or really in an SO answer.
anon
well anyway, thank you very much! I guess I have to devote some time to templates but this will not make it into the code just yet. I am really quite happy with the map right now.
Arne
+1  A: 

One pattern is to have your derived classes register themselves with the factory during static initialization, so that you build up a list of all of the derived classes in existence. When parsing your XML file, you look up the string in the data structure to determine the correct derived implementation to use.

Commodore Jaeger
Thanks for your commen! Could you explain how this registering process would be done?
Arne