Wo kann ich Einstellungen aus einer Datei laden und speichern?

9

Ich denke, diese Frage sollte für die meisten Programme gelten, die Einstellungen aus einer Datei laden. Meine Frage ist aus programmtechnischer Sicht und es geht wirklich darum, wie man mit dem Laden von Einstellungen aus einer Datei in Bezug auf verschiedene Klassen und Zugänglichkeit umgeht. Zum Beispiel:

  • Wenn ein Programm eine einfache settings.iniDatei hatte, sollte sein Inhalt in eine load()Methode einer Klasse oder vielleicht in den Konstruktor geladen werden ?
  • Sollten die Werte in public staticVariablen gespeichert werden oder sollten staticMethoden zum Abrufen und Festlegen von Eigenschaften vorhanden sein?
  • Was soll passieren, wenn die Datei nicht vorhanden oder nicht lesbar ist? Wie würden Sie den Rest des Programms wissen lassen, dass es diese Eigenschaften nicht erhalten kann?
  • etc.

Ich hoffe, ich frage das hier an der richtigen Stelle. Ich wollte die Frage so sprachunabhängig wie möglich machen, aber ich konzentriere mich hauptsächlich auf Sprachen, die Dinge wie Vererbung haben - insbesondere Java und C # .NET.

Andy
quelle
1
Für .NET ist es am besten, App.config und die System.Configuration.ConfigurationManager-Klasse zu verwenden, um die Einstellungen abzurufen
Gibson
@Gibson Ich fange gerade in .NET an. Könnten Sie mich also mit guten Tutorials für diese Klasse verknüpfen?
Andy
Stackoverflow hat viele Antworten darauf. Die Schnellsuche zeigt: stackoverflow.com/questions/114527/… und stackoverflow.com/questions/13043530/… Es wird auch viele Informationen zu MSDN geben, beginnend hier msdn.microsoft.com/en-us/library/ms228063(v = vs.100) .aspx und msdn.microsoft.com/en-us/library/ms184658(v=vs.110).aspx
Gibson
@ Gibson Vielen Dank für diese Links. Sie werden sehr nützlich sein.
Andy

Antworten:

8

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.

Benedikt
quelle
+1 Danke für die Antwort. Früher, wenn ich Benutzereinstellungen gespeichert habe, habe ich gerade aus einer Datei wie einer gelesen, .inidamit sie für Menschen lesbar ist. Schlagen Sie jedoch vor, eine Klasse mit den Variablen in zu serialisieren?
Andy
.ini ist einfach zu analysieren, und es gibt auch viele APIs, die .ini als mögliches Format enthalten. Ich schlage vor, dass Sie solche APIs verwenden, um die Grunzarbeit der Textanalyse zu erledigen, aber das Endergebnis sollte sein, dass Sie eine POD-Klasse initialisiert haben. Ich verwende den Begriff Serialisierung im allgemeinen Sinne des Kopierens oder Lesens in die Felder einer Klasse aus einem bestimmten Format, nicht die spezifischere Definition des Serialisierens einer Klasse direkt in / aus der Binärdatei (wie dies normalerweise für java.io.Serializable verstanden wird ).
Benedict
Jetzt verstehe ich etwas besser. Sie sagen also im Wesentlichen, dass Sie eine Klasse mit allen Feldern / Einstellungen haben und eine andere, um die Werte in dieser Klasse aus der Datei festzulegen? Wie soll ich dann die Werte aus der ersten Klasse in anderen Klassen erhalten?
Andy
Von Fall zu Fall. Einige Ihrer Klassen der obersten Ebene benötigen möglicherweise nur einen Verweis auf die gesamte an sie übergebene Konfigurationsinstanz, wenn sie auf so vielen Parametern beruhen. Andere Klassen benötigen möglicherweise nur einen oder zwei Parameter, aber es ist nicht erforderlich, diese mit dem Config-Objekt zu koppeln (was das Testen noch einfacher macht). Geben Sie einfach die wenigen Parameter durch Ihre Anwendung weiter. Wie in meiner Antwort erwähnt, ist es im Allgemeinen nicht schwierig, die Werte dort zu erhalten, wo Sie sie benötigen, wenn Sie mit einer testbaren / DI-orientierten Architektur bauen. Verwenden Sie den globalen Zugriff auf eigene Gefahr.
Benedict
Wie würden Sie also vorschlagen, dass das Konfigurationsobjekt an Klassen übergeben wird, wenn die Vererbung nicht beteiligt sein muss? Durch einen Konstruktor? In der Hauptmethode des Programms wird also die Reader-Klasse initiiert, die Werte in der Config-Klasse speichert. Dann übergibt die Hauptmethode das Config-Objekt oder einzelne Variablen an andere Klassen.
Andy
4

Im Allgemeinen (meiner Meinung nach) ist es am besten, die Anwendung die Speicherung der Konfiguration behandeln zu lassen und die Konfiguration an Ihre Module zu übergeben. Dies ermöglicht Flexibilität bei , wie die Einstellungen gespeichert werden , so dass Sie Dateien oder Webdiensten oder Datenbanken Ziel oder ...

Außerdem trägt die Anwendung die Last "Was passiert, wenn Dinge fehlschlagen", wer am besten weiß, was dieser Fehler bedeutet.

Und es macht es viel einfacher, Unit-Tests durchzuführen, wenn Sie nur ein Konfigurationsobjekt übergeben können, anstatt das Dateisystem berühren oder Parallelitätsprobleme lösen zu müssen, die durch statischen Zugriff entstehen.

Telastyn
quelle
Vielen Dank für Ihre Antwort, aber ich verstehe es nicht ganz. Ich spreche beim Schreiben der Anwendung darüber, dass es nichts gibt, was mit der Konfiguration zu tun hat, und daher weiß ich als Programmierer, was ein Fehler bedeutet.
Andy
@andy - sicher, aber wenn die Module direkt auf die Konfiguration zugreifen, wissen sie nicht, in welchem ​​Kontext sie arbeiten, sodass sie nicht bestimmen können, wie sie mit Konfigurationsproblemen umgehen sollen. Wenn die Anwendung das Laden ausführt, kann sie alle Probleme lösen, da sie den Kontext kennt. Einige Apps möchten möglicherweise auf einen Standardwert
umschalten
Um dies hier zu verdeutlichen, beziehen Sie sich nach Modulen auf Klassen oder tatsächliche Erweiterungen eines Programms? Weil ich frage, wo Einstellungen am besten tatsächlich im Code des Hauptprogramms gespeichert werden können und wie anderen Klassen der Zugriff auf die Einstellungen ermöglicht werden kann. Ich möchte also eine Konfigurationsdatei laden und die Einstellung dann irgendwo / irgendwie speichern, damit ich nicht weiter auf die Datei verweisen muss.
Andy
0

Wenn Sie .NET zum Programmieren der Klassen verwenden, haben Sie verschiedene Optionen wie Ressourcen, web.config oder sogar eine benutzerdefinierte Datei.

Wenn Sie Resources oder web.config verwenden, werden die Daten tatsächlich in einer XML-Datei gespeichert, das Laden ist jedoch schneller.

Das Abrufen der Daten aus diesen Dateien und das Speichern an einem anderen Speicherort entspricht einer doppelten Verwendung des Speichers, da diese standardmäßig in den Speicher geladen werden.

Für jede andere Datei oder Programmiersprache funktioniert die obige Antwort von Benedict.

Kiran
quelle
Vielen Dank für Ihre Antwort und Entschuldigung für die langsame Antwort. Die Verwendung der Ressourcen wäre eine gute Option, aber ich habe gerade festgestellt, dass dies wahrscheinlich nicht die beste Option für mich ist, da ich vorhabe, dasselbe Programm in anderen Sprachen zu entwickeln. Daher hätte ich lieber eine XML- oder JSON-Datei Lesen Sie es in meiner eigenen Klasse, damit dieselbe Datei in anderen Programmiersprachen gelesen werden kann.
Andy