Dies ist eigentlich eine wirklich wichtige Frage und wird oft falsch gemacht, da ihr nicht genügend Bedeutung beigemessen wird, obwohl sie ein Kernbestandteil so ziemlich jeder Anwendung ist. Hier sind meine Richtlinien:
Ihre Konfigurationsklasse, die alle Einstellungen enthält, sollte nur ein einfacher alter Datentyp sein, struct / class:
class Config {
int prop1;
float prop2;
SubConfig subConfig;
}
Es sollte keine Methoden benötigen und keine Vererbung beinhalten (es sei denn, es ist die einzige Wahl, die Sie in Ihrer Sprache zur Implementierung eines Variantenfelds haben - siehe nächster Absatz). Es kann und sollte Komposition verwenden, um die Einstellungen in kleinere spezifische Konfigurationsklassen zu gruppieren (z. B. subConfig oben). Wenn Sie dies auf diese Weise tun, ist es ideal, Unit-Tests und die Anwendung im Allgemeinen weiterzugeben, da sie nur minimale Abhängigkeiten aufweist.
Sie müssen wahrscheinlich Variantentypen verwenden, falls Konfigurationen für verschiedene Setups heterogen aufgebaut sind. Es wird davon ausgegangen, dass Sie irgendwann eine dynamische Umwandlung vornehmen müssen, wenn Sie den Wert lesen, um ihn in die richtige (Unter-) Konfigurationsklasse umzuwandeln. Dies hängt zweifellos von einer anderen Konfigurationseinstellung ab.
Sie sollten nicht faul sein, alle Einstellungen als Felder einzugeben, indem Sie einfach Folgendes tun:
class Config {
Dictionary<string, string> values;
};
Dies ist verlockend, da Sie eine verallgemeinerte Serialisierungsklasse schreiben können, die nicht wissen muss, mit welchen Feldern sie sich befasst, aber es ist falsch und ich werde gleich erklären, warum.
Die Serialisierung der Konfiguration erfolgt in einer völlig separaten Klasse. Unabhängig davon, welche API oder Bibliothek Sie dazu verwenden, sollte der Hauptteil Ihrer Serialisierungsfunktion Einträge enthalten, die im Grunde genommen einer Zuordnung vom Pfad / Schlüssel in der Datei zum Feld auf dem Objekt entsprechen. Einige Sprachen bieten eine gute Selbstbeobachtung und können dies sofort für Sie tun, andere müssen Sie das Mapping explizit schreiben, aber das Wichtigste ist, dass Sie das Mapping nur einmal schreiben müssen. Betrachten Sie beispielsweise diesen Auszug, den ich aus der Parser-Dokumentation zu den C ++ - Boost-Programmoptionen angepasst habe:
struct Config {
int opt;
} conf;
po::options_description desc("Allowed options");
desc.add_options()
("optimization", po::value<int>(&conf.opt)->default_value(10);
Beachten Sie, dass in der letzten Zeile grundsätzlich "Optimierung" für Config :: opt steht und dass eine Deklaration des erwarteten Typs vorhanden ist. Sie möchten, dass das Lesen der Konfiguration fehlschlägt, wenn der Typ nicht Ihren Erwartungen entspricht, wenn der Parameter in der Datei nicht wirklich ein float oder ein int ist oder nicht vorhanden ist. Dh Fehler sollte auftreten, wenn Sie die Datei lesen, da das Problem im Format / in der Validierung der Datei liegt und Sie einen Ausnahme- / Rückkehrcode auslösen und das genaue Problem melden sollten. Sie sollten dies nicht zu einem späteren Zeitpunkt im Programm verzögern. Das ist der Grund, warum Sie nicht versucht sein sollten, die oben erwähnte Conf im Dictionary-Stil abzufangen, die beim Lesen der Datei nicht fehlschlägt - da das Casting verzögert wird, bis der Wert benötigt wird.
Sie sollten die Config-Klasse in gewisser Weise schreibgeschützt machen - indem Sie den Inhalt der Klasse beim Erstellen einmal festlegen und aus der Datei initialisieren. Wenn Sie dynamische Einstellungen in Ihrer Anwendung benötigen, die sich ändern, sowie konstante Einstellungen, die dies nicht tun, sollten Sie eine separate Klasse haben, um die dynamischen Einstellungen zu verarbeiten, anstatt zu versuchen, zuzulassen, dass Bits Ihrer Konfigurationsklasse nicht schreibgeschützt sind .
Idealerweise lesen Sie die Datei an einer Stelle in Ihrem Programm ein, dh Sie haben nur eine Instanz eines " ConfigReader
". Wenn Sie jedoch Schwierigkeiten haben, die Config-Instanz an den gewünschten Ort weiterzuleiten, ist es besser, einen zweiten ConfigReader zu haben, als eine globale Konfiguration einzuführen (was das OP vermutlich unter "statisch" versteht "), was mich zu meinem nächsten Punkt bringt:
Vermeiden Sie das verführerische Sirenenlied des Singletons: "Ich erspare Ihnen, dass Sie diese Klassenklasse weitergeben müssen, alle Ihre Konstrukteure werden nett und sauber sein. Weiter, es wird so einfach sein." Die Wahrheit ist, dass Sie mit einer gut gestalteten testbaren Architektur kaum die Config-Klasse oder Teile davon durch so viele Klassen Ihrer Anwendung weitergeben müssen. Was Sie in Ihrer Top-Level-Klasse, Ihrer main () -Funktion oder was auch immer finden, entwirren Sie die conf in einzelne Werte, die Sie Ihren Komponentenklassen als Argumente zur Verfügung stellen, die Sie dann wieder zusammensetzen (manuelle Abhängigkeit) Injektion). Ein singleton / global / static conf erschwert das Implementieren und Verstehen von Unit-Tests Ihrer Anwendung erheblich. Dies verwirrt beispielsweise neue Entwickler in Ihrem Team, die nicht wissen, dass sie den globalen Status zum Testen von Inhalten festlegen müssen.
Wenn Ihre Sprache Eigenschaften unterstützt, sollten Sie diese für diesen Zweck verwenden. Der Grund dafür ist, dass es sehr einfach ist, abgeleitete Konfigurationseinstellungen hinzuzufügen, die von einer oder mehreren anderen Einstellungen abhängen. z.B
int Prop1 { get; }
int Prop2 { get; }
int Prop3 { get { return Prop1*Prop2; }
Wenn Ihre Sprache die Eigenschaftssprache nicht nativ unterstützt, kann es eine Problemumgehung geben, um den gleichen Effekt zu erzielen, oder Sie erstellen einfach eine Wrapper-Klasse, die die Bonuseinstellungen bereitstellt. Wenn Sie den Vorteil von Eigenschaften nicht anderweitig nutzen können, ist es ansonsten Zeitverschwendung, manuell zu schreiben und Getter / Setter zu verwenden, nur um einem OO-Gott zu gefallen. Mit einem einfachen alten Feld sind Sie besser dran.
Möglicherweise benötigen Sie ein System, um mehrere Konfigurationen von verschiedenen Orten in der Reihenfolge ihrer Priorität zusammenzuführen und zu übernehmen. Diese Rangfolge sollte für alle Entwickler / Benutzer klar definiert und verständlich sein, z. B. die Windows-Registrierung HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE berücksichtigen. Sie sollten diesen funktionalen Stil ausführen, damit Ihre Konfigurationen schreibgeschützt bleiben, dh:
final_conf = merge(user_conf, machine_conf)
eher, als:
conf.update(user_conf)
Abschließend möchte ich natürlich hinzufügen, dass Sie, wenn Ihr ausgewähltes Framework / Ihre Sprache eigene integrierte, bekannte Konfigurationsmechanismen bietet, die Vorteile dieser Verwendung in Betracht ziehen sollten, anstatt Ihre eigenen zu rollen.
So. Viele Aspekte, die berücksichtigt werden müssen - machen Sie es richtig und es wird Ihre Anwendungsarchitektur tiefgreifend beeinflussen, Fehler reduzieren, Dinge leicht testbar machen und Sie dazu zwingen, gutes Design an anderer Stelle zu verwenden.