Hej!Vereinfachte Konfig-Datei-gesteuerte Fabrik
Vorsicht das ist ein langer Post - wenn Sie leicht genervt sind besser überspringen Sie es. ;-)
Bei dem Projekt, an dem ich gerade arbeite, geht es darum, Spannungsmessungen verschiedener Sensoren zu lesen und den jeweiligen physikalischen Wert zu berechnen. Es gibt eine Anzahl von Kanälen, an denen jeweils ein anderer Sensor angebracht sein kann. Grundsätzlich ist die Art des Sensors bekannt, aber die Details wie Empfindlichkeit und Verzerrung können sehr unterschiedlich sein. Während für jeden Sensortyp eine parametrisierte Klasse existiert, werden die Parameter aus den Konfigurationsdateien gezogen.
Angenommen, es gibt die folgende Klassenhierarchie ist:
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);
};
Die Hauptkonfigurationsdatei sieht wie folgt aus:
<channels>
<TurbineInletTemp sensor="Thermocouple.TypeK.xml" />
<CylinderHeadTemp sensor="PTCResistor.PT500.xml" />
...
</channels>
Wie Sie ein Attribut Sensor sehen jeder Kanal-Tag hat einige ohter XML-Datei angeben welche Parameterinformation und die Art des Sensors. Es sieht etwa so aus:
<sensor class="PTCResistor">
<param Rref="500" Tref="0">
...
</sensor>
Das Format dieser Referenz XML-Dateien variieren kann, jedoch wird durch den Konstruktor jeder abgeleiteten Klasse verstanden.
Beim Programmstart wird die Hauptkonfigurationsdatei analysiert und eine Factory-Klasse löst die Verknüpfung zu den XMLs auf, die die sensorspezifischen Informationen enthalten, untersucht das Klassen-Tag und ruft die entsprechenden Konstruktoren auf. Wie folgt aus:
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);
}
Dies ist die schlanksten Lösung, die ich mit in dem Sinn kam, dass es nur wenige Orte sind zu ändern, wenn eine neue abgeleitete Klasse hinzugefügt wird. Im Grunde schreibt man eine neue abgeleitete Klasse und fügt einen weiteren if-Zweig in der Fabrik hinzu. Leider beinhaltet dies viele String-Vergleiche. Da die Anzahl der Klassen und Kanäle ziemlich groß sein kann (und das Zielsystem schwach ist), bin ich besorgt.
Eine andere Möglichkeit wäre Hash der Klassennamen. Dies würde wahrscheinlich wie folgt aussehen:
Problem hier: müssen die enum die Hash-Werte enthalten pflegen.
Während ich diesen Code schreibe, fühle ich, dass der Prozess des Hinzufügens eines neuen Sensors ziemlich mühsam sein kann. Irgendwelche Ideen, wie man das Durcheinander reduzieren kann? Oder ist das schon das kleinste Übel, das man bekommen kann?
Danke fürs Lesen! Arne
EDIT1
Wie Neil Butter vorgeschlagen halte ich durch eine Funktion wie
static Sensor* Thermocouple::Create(XMLNode &node) {
return new Thermocouple(node);
}
jede Klasse zu erweitern und eine Hash-Tabelle erstellen, die den Namen der Klasse String in einen Funktionszeiger auf diese bezieht Statische Funktion:
Damit kann das passende Sensorobjekt für einen namentlich gekennzeichneten Kanal erstellt werden.Die Suche wird durch Hash-Vergleich durchgeführt und man muss nur die Map-Initialisierung im Konstruktor der Factory-Klasse beibehalten.
Ich habe jede Fehlerüberprüfung absichtlich weggelassen, um den Code nicht noch mehr zu verwirren. Außerdem habe ich den obigen Code nicht zusammengestellt, da ich gerade nicht arbeite. – Arne
classNameMap [Thermoelement] sollte classNameMap ["Thermocouple"] Ich denke - oder vielleicht geben Sie der Klasse eine statische Classname-Funktion, also dann becmes classNameMap [Thermocouple :: ClassName()] –
ja, danke. scheint ich schlampig – Arne