Einstellungsverwaltung in der Applikation

Rund um die LCL und andere Komponenten
Antworten
charlytango
Beiträge: 843
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz stable (2.2.6, 3.x)
CPU-Target: Win 32/64, Linux64
Wohnort: Wien

Einstellungsverwaltung in der Applikation

Beitrag von charlytango »

Hi,

Einstellungen die ich applikationsweit benötige, verwalte ich in einem TMemdataset.
Initialisierung, Zugriff, etc habe ich bisher in einem Datamodul gemacht das mit der Applikation erzeugt wurde.

Code: Alles auswählen

 
unit udmSettings;
....
type
  TdmSettings = class(TDataModule)
  ...
end;
 
var
  dmSettings: TdmSettings;
 
implementation
....


in der *.lpr Datei:

Code: Alles auswählen

 
begin
  RequireDerivedFormResource := True;
  Application.Initialize;
  ....
  Application.CreateForm(TdmSettings, dmSettings)//Einstellungen verwalten
  Application.CreateForm(TfrmMain, frmMain);          //Hauptformular erzeugen
  Application.Run;
end.


Somit war alles was im Datenmodul behandelt wurde auch applikationsweit über die Variable dmSettings verwendbar/sichtbar - es musste nur das Datenmodul per uses udmSettings in die jeweilige Unit eingebunden werden.

Nun hat jede Applikation so ihre kleinen Eigenheiten und man kann nicht immer alles über einen Kamm scheren.
Bislang habe ich dann das Datenmodul in dem die Funktionen lagen vererbt und abweichendes Verhalten im Nachfolger eingebaut.
Jetzt hatte schon das alte Delphi mit der Vererbung von Datenmodulen seine liebe Not und Lazarus scheint da auch noch eine Macke zu haben.
Gelegentlich findet die IDE zusammengehörende Komponenten dann nicht mehr und zerschießt die Vererbung -- kann auch sein dass ich dazu zu dämlich bin, unschön ist diese Lösung allemal.

Ich versuchte jetzt alle nötigen Funktionen direkt in eine Unit zu packen (ohne *.lfm) und alle benötigten Komponenten selbst zu erstellen und ggfs auch zuzuweisen.
Passt auch, soweit ich das bis jetzt beurteilen kann.

Nur muss ich das Objekt mit den Einstellungen aus der Klasse selbst erzeugen und damit bin ich mit meinem Latein irgendwie am Ende.
Dabei geht es mir nicht darum wie ich ein Objekt erzeuge sondern um die Sichtbarkeit des erzeugten Objektes in der Applikation.

Wo bzw wie erzeuge ich das Objekt das meine Einstellungen verwaltet damit alle Units und Forms darauf zur Laufzeit zugreifen können?
Gibt es da eine ebenso elegante Lösung wie die mit den Datamodulen ?

Oder muss ich es im Hauptformular erzeugen und alle Units müssen das Hauptformular mit eingebunden haben damit man darauf zugreifen kann?
Oder eine allgemeine Objektliste auf die man irgendwie kommt?

Die Anforderung ist sicher nicht sehr exotisch, Programmparameter braucht fast jeder -- aber wie löst ihr das elegant?

Danke im voraus!

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Einstellungsverwaltung in der Applikation

Beitrag von Warf »

Zwei Optionen: 1. Singelton:

Code: Alles auswählen

Unit Settings;
...
type
  TSettings = class
  ...
  end;
 
function Settings: TSettings;
 
implementation
function Settings: TSettings;
const _Settings: TSettings = nil; // globale variable die Prozedur lokal ist
begin
  if not Assigned(_Settings) then
  begin
    _Settings = TSettings.Create(...);
    // erstes mal aufrufen: Lade Einstellungen
  end;
  Result := _Settings
end;
 
end.


2. Initialization verwenden:

Code: Alles auswählen

Unit Settings;
...
type
  TSettings = class
  ...
  end;
var Settings: TSettings;
 
implementation
 
initialization // wird beim laden der Unit ausgeführt -> bei Programmstart
  Settings = TSettings.Create(...);
  // Settings laden
end.


beides lässt sich mit dem Finalization block kombinieren falls am ende des Programmes noch aufgeräumt werden muss:

Code: Alles auswählen

Unit Settings;
...
type
  TSettings = class
  ...
  end;
var Settings: TSettings;
 
implementation
 
initialization // wird beim laden der Unit ausgeführt -> bei Programmstart
  Settings = TSettings.Create(...);
  // Settings laden
finalization
  Settings.Free;
end.


Der singelton hat den Vorteil das die Settings nur geladen werden wenn auch darauf zugegriffen wird, die Initializaition wird immer beim Programmstart ausgeführt sobald die Unit irgendwo in den uses steht

charlytango
Beiträge: 843
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz stable (2.2.6, 3.x)
CPU-Target: Win 32/64, Linux64
Wohnort: Wien

Re: Einstellungsverwaltung in der Applikation

Beitrag von charlytango »

erstmal danke - wenn du das so einfach hinskizzierst, hab ich einige Wissenslücken durch die ein ICE quer passt ;)

Da werde ich mich mal damit beschäftigen
Merci vielmals !

charlytango
Beiträge: 843
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz stable (2.2.6, 3.x)
CPU-Target: Win 32/64, Linux64
Wohnort: Wien

Re: Einstellungsverwaltung in der Applikation

Beitrag von charlytango »

So, nach Recherche und der Empfehlung von einigen Postern die Finger von einem Singleton zu lassen solange es geht (weil der Code nach längerer Zeit wohl schwerer zu durchschauen ist) habe ich mit der 2. Variante "Initialisation" getestet.
Funktioniert perfekt und wie gewünscht. Das Objekt wird nur einmal erzeugt, auch wenn es in den uses-klauseln von verschiedenen Units referenziert wird.

Allerdings tritt dabei das Problem auf, dass ich nicht kontrollieren kann wann das Objekt erzeugt wird.

Warf hat geschrieben:.. die Initializaition wird immer beim Programmstart ausgeführt sobald die Unit irgendwo in den uses steht


Denn ggfs muss man das Objekt im zeitlichen Ablauf des gesamten Programmes mit Werten versorgen damit nachfolgende Funktionen sich darauf beziehen können.
Wenn ich das zeitlich nicht kontrollieren kann, wird das böse enden. Da nun die DB-Anbindung nicht mehr als Datamodule in den Formularen eines Projekts gelistet wird, kann ich sie auch nicht einfach in eine Reihenfolge im Projektfile (.lpr) bringen.

Gibt es da andere Lösungen ? Oder gibt es einen definierten Zeitpunkt an dem solche Objekte erzeugt werden?

Zudem wäre es auch fein wenn man dem Benutzer während der Erstellung der Objekte (z.B. Batenbankanbindung etc) in einer Splashscreen informieren könnte.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6198
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Einstellungsverwaltung in der Applikation

Beitrag von af0815 »

Ich nehme da die Singletonlösung, damit kann ich in meinem Hauptformular alles sauber und richtig initialisieren. Beispiel, wennich die Parameter für die DB Verbindung aus einer Lokalen DB holen muss. Und ein Singleton ist per se nicht böse oder unūbersichtlich, wenn der restliche Code auch verständlich ist. Bei mir ist allerdings der Startupcode immer zusammenhängend im Main und nicht irgendwie fragmentiert im Programm verstreut.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Einstellungsverwaltung in der Applikation

Beitrag von Warf »

charlytango hat geschrieben:Denn ggfs muss man das Objekt im zeitlichen Ablauf des gesamten Programmes mit Werten versorgen damit nachfolgende Funktionen sich darauf beziehen können.
Wenn ich das zeitlich nicht kontrollieren kann, wird das böse enden. Da nun die DB-Anbindung nicht mehr als Datamodule in den Formularen eines Projekts gelistet wird, kann ich sie auch nicht einfach in eine Reihenfolge im Projektfile (.lpr) bringen.

Gibt es da andere Lösungen ? Oder gibt es einen definierten Zeitpunkt an dem solche Objekte erzeugt werden?

Zudem wäre es auch fein wenn man dem Benutzer während der Erstellung der Objekte (z.B. Batenbankanbindung etc) in einer Splashscreen informieren könnte.


Wenn das Hauptprogramm startet, werden zunächst alle Units die genutzt werden in der Reihenfolge Initialisiert wie sie verwendet werden. Z.B. benutzt mein Programm Unit1 und Unit2 in der reihenfolge. Unit1 verwendet Unit3, und Unit2 verwendet Unit4. Dann muss zu erst Unit3 Initializiert werden, damit Unit1 Initialisiert werden kann, dann Unit4 und Schließlich Unit2.
In deinem Initialization code kannst du daher davon ausgehen das jede Unit die deine Initialisation benutzt bereits initalisiert wurde. Genauso kannst du davon ausgehen das an jeder Position einer beliebigen Unit, welche die Zielunit in den Uses hat, der initalization part bereits aufgerufen wurde. Es wird also garantiert das der initialization block ausgeführt wird bevor irgendeine funktion dieser Unit (sowie irgendeiner Unit die diese Unit verwendet) aufgerufen werden kann.

Was komplizierter ist, ist zu steuern welcher Initalisierungsteil zu erst stattfinden muss. Wenn z.B. Unit1 nach Unit2 initialisieren muss, kann man entweder Unit2 in die uses von Unit1 packen (wobei ich nicht weiß ob der Smartlinker das nicht einfach rauswirft wenn außer der initialization keine abbhängigkeit besteht) oder im Main projekt zu erst Unit2 danach Unit1 in der Uses klausel haben. Es sollte aber niemals vorkommen das eine Funktion vor dem initialize ausgeführt werden kann.

PS: zu dem Singelton, das mag vielleicht "kompliziert" aussehen, aber nicht vergessen, es ist alles innerhalb einer Funktion. Zwischen Initialize und der Definition der Globalen Variable steht der gesammte Implementation Teil der Unit. Noch dazu kann der Intialization Teil beliebig groß und komplex werden. Das heißt wenn du was an der Variable ändern willst kann es sein das du erst mal die halbe Unit durchsuchen musst ums zu finden.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Einstellungsverwaltung in der Applikation

Beitrag von braunbär »

Wir hatten vor Kurzem einen anderen Thread zum Thema "ist OOP tot?", oder so ähnlich.
Dem habe ich zwar widersprochen, aber es gibt schon OOP-Konzepte, die m.E. überhaupt keinen Sinn haben. Das Singleton ist ein solches. Wozu soll eine Klasse gut sein, wenn es das Objekt nur ein einziges mal im ganzen Projekt gibt. Warum soll man nicht einen guten alten record nehmen? Nur, weil Klasse mehr nach OOP klingt?
Initialisierung im OnCreate des Hauptformulars der Applikation aufrufen, oder, wenn möglich, in der Initialization der Unit sebst, wenn zu dem Zeitpunkt schon alle nötigen Daten vorhanden sind (wenn z.B. eine DB-Verbindung gebraucht wird, wird die zu dem Zeitpunkt wahrscheinlich noch nicht verfügbar sein)

compmgmt
Beiträge: 351
Registriert: Mi 25. Nov 2015, 17:06
OS, Lazarus, FPC: Win 10 Pro | Lazarus 1.8.2 | FPC 3.0.4
CPU-Target: i386 + x86_64
Wohnort: in der Nähe von Stuttgart
Kontaktdaten:

Re: Einstellungsverwaltung in der Applikation

Beitrag von compmgmt »

braunbär hat geschrieben:Warum soll man nicht einen guten alten record nehmen? Nur, weil Klasse mehr nach OOP klingt?
Weil bei der Zuweisung einer Instanz nur der Pointer übergeben wird, bei Records alle Daten kopiert werden.

Code: Alles auswählen

type
  TMyClass = class
    private
      FElement: String;
    public
      property Element: String read FElement write FElement;
  end;
 
  TMyRecord = record
    Element: String;
  end;
 
var
  c1, c2: TMyClass;
  r1, r2: TMyRecord;
 
begin
  c1 := TMyClass.Create;
  c1.Element := 'Default';
  c2 := c1; // c2 wird c1 zugewiesen (Pointerzuweisung)
  c1.Element := 'Changed'; // Ändere Element von c1
  WriteLn(c2.Element); // Gebe Element von c2 aus. Da c1 = c2 wird 'Changed' ausgegeben
  c1.Free;
 
  r1.Element := 'Default';
  r2 := r1; // r2 wird r1 zugewiesen (Kopieren aller Daten)
  r1.Element := 'Changed'; // Ändere Element von r1
  WriteLn(r2.Element); // Gebe Element von r2 aus. Da r1 <> r2 wird 'Default' ausgegeben
 
  ReadLn;
end.
Ausgabe:

Code: Alles auswählen

Changed
Default


Wenn ich globale Einstellungen als Klasse habe kann ich folgendes machen:

Code: Alles auswählen

var
  s: TMySettings; // class
begin
  s := MySettings; // MySettings nehme ich jetzt mal als Singleton
  s.Irgendwas := Wert;
end;
Mit Records geht das ganze nicht da alle Daten beim Zuweisen kopiert werden was außerdem auch bei größeren records deutlich länger dauert. Würdest du unbedingt einen Record haben wollen müsstest du das so lösen:

Code: Alles auswählen

var
  s: ^TMySettings; // record
begin
  s := @MySettings;
  s^.Irgendwas := Wert;
end;

Code: Alles auswählen

InitiateSystemShutdownExA(nil, nil, 0, true, false, $0005000F);
Have fun with this snippet ;)

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Einstellungsverwaltung in der Applikation

Beitrag von braunbär »

compmgmt hat geschrieben:Wenn ich globale Einstellungen als Klasse habe kann ich folgendes machen:

Code: Alles auswählen

var
  s: TMySettings; // class
begin
  s := MySettings; // MySettings nehme ich jetzt mal als Singleton
  s.Irgendwas := Wert;
end;
Mit Records geht das ganze nicht da alle Daten beim Zuweisen kopiert werden was außerdem auch bei größeren records deutlich länger dauert. Würdest du unbedingt einen Record haben wollen müsstest du das so lösen:

Code: Alles auswählen

var
  s: ^TMySettings; // record
begin
  s := @MySettings;
  s^.Irgendwas := Wert;
end;


Was hindert dich darann, einfach zu schreiben

Code: Alles auswählen

 
MySettings.Irgendwas := Wert
 

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Einstellungsverwaltung in der Applikation

Beitrag von Warf »

braunbär hat geschrieben:Wir hatten vor Kurzem einen anderen Thread zum Thema "ist OOP tot?", oder so ähnlich.
Dem habe ich zwar widersprochen, aber es gibt schon OOP-Konzepte, die m.E. überhaupt keinen Sinn haben. Das Singleton ist ein solches. Wozu soll eine Klasse gut sein, wenn es das Objekt nur ein einziges mal im ganzen Projekt gibt. Warum soll man nicht einen guten alten record nehmen? Nur, weil Klasse mehr nach OOP klingt?
Initialisierung im OnCreate des Hauptformulars der Applikation aufrufen, oder, wenn möglich, in der Initialization der Unit sebst, wenn zu dem Zeitpunkt schon alle nötigen Daten vorhanden sind (wenn z.B. eine DB-Verbindung gebraucht wird, wird die zu dem Zeitpunkt wahrscheinlich noch nicht verfügbar sein)


In pascal ists ein bisschen witzlos, da du nicht verbieten kannst (zumindest beim fpc nicht, bei delphi keine Ahnung) Objekte zu erstellen. Normalerweise benutzt man singeltons so (ähnlich):

Code: Alles auswählen

  TTestClass = class
    strict private
      constructor Create;
    public
      class function get: TTestClass;
  end;
 
implementation
 
{ TTestClass }
 
constructor TTestClass.Create;
begin
...
end;
 
class function TTestClass.get: TTestClass;
const instance: TTestClass = nil;
begin
  if not Assigned(instance) then instance:=TTestClass.Create;
  Result := instance;
end;


Die Idee ist jetzt das niemand außer der Class Funktion überhaupt Objekte vom typem TTestClass erstellen kann (in pascal etwas witzlos da der Default Constructor immer accessible ist). Und die Idee ist ganz einfach, wenn ich dem Nutzer verbiete mit einem Objekt scheiße zu bauen, baut niemand scheiße und alle sind glücklich. In C++ oder Java kannst du es so praktisch machen das es immer und auf jeden fall nur dieses eine Objekt des Typen TTestClass geben wird und nie ein zweites.
Das Problem bei einfach einem Öffentlichen Record ist ganz einfach:

Code: Alles auswählen

var rec: TTestRec;
rec := MyGlobalRec; // von jetzt an gibts 2
.
Sobald du zwei hast kannst du nicht mehr von einer gewissen atomarität ausgehen. z.B. Konfigurationsdateien, du hast implementiert das Sobald die konfig sich verändern die auf die Platte geschrieben wird (um bei einem Crash die letzte Konfig zu erhalten). Ist ein ziemlich gängiges Problem.

Code: Alles auswählen

var conf: TConfRec;
conf := GlobalConfRec;
...
GlobalConfRec.Foo := 'Bar';
...
conf.Bar := 'Foo'; // das überschreibt jetzt die Config von oben


Da muss nur einer bei der Parameterübergabe einer Funktion das var vergessen haben, oder generell irgendwo jemand nicht aufgepasst und zack hat man den Salat.

Ein Singelton ist kein allheilmittel, und man sollte sich wirklich überlegen ob man ihn wirklich braucht, aber wenn es für die Funktionalität deines Programmes gewährleistet sein muss das nur eine Instanz des Objektes exsistiert, dann ist es genau das was man sucht. Genauso wenn man zugriff nur über Singelton erlaubt, kommt auch niemand auf die Idee die klasse plötzlich umzubauen und damit eventuell bestehende konzepte die die einmaligkeit der Instanz vorraussetzen zu zerstören

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Einstellungsverwaltung in der Applikation

Beitrag von braunbär »

Es ist aber eben nicht möglich, alle Blödheiten, die einem Programmierer einfallen können, abzufangen. Und ein Szenario, in dem jemand einen zweiten Record erstellt, ohne es zu bemerken, kann ich mir schwer vorstellen.
So ist es z.B. völlig sinnlos, einen globalen Record als Parameter an eine Prozedur zu übergeben (werde als const noch als sonstwas), wenn es by Design überhaupt nur eine Recordinstanz des Typs im Programm geben soll, weil dieser Record in der Prozedur genauso gut oder vielmehr besser direkt angesprochen wird (besser, weil niemand sich beim Durchlesen der Prozedur den Kopf darüber zu zerbrechen braucht, ob vielleicht irgendwo doch etwas anderes als dieses "Singleton" als Parameter übergeben wird). Ebenso ist es sinnlos, eine lokale Variable dieses Recordtyps zu erstellen. Wenn ein Programmierer das macht, dann wird es im Programm ziemlich sicher noch ernstere Probleme geben als die Fehler mit dem Singleton.

Es gibt eine Menge Fehler, die ein Programmierer irrtümlich machen kann, gegen die sollte man ein Programm wenn möglich absichern. Aber gegen Fehler aus reiner Blödheit kann man nicht viel machen, und jeder Overhead, der darauf abzielt, ist m.E. überflüsssig..

Warf hat geschrieben:Genauso wenn man zugriff nur über Singelton erlaubt, kommt auch niemand auf die Idee die klasse plötzlich umzubauen und damit eventuell bestehende konzepte die die einmaligkeit der Instanz vorraussetzen zu zerstören

Ein Kommentar mit einer auffälligen Warnung bei der Record-Definition sollte es eigentlich auch tun, wenn die Leute, die an dem Projekt arbeiten, halbwegs bei Trost sind, oder?

BeniBela
Beiträge: 308
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: Einstellungsverwaltung in der Applikation

Beitrag von BeniBela »

Singleton:

Code: Alles auswählen

var globalSettings: record
   ....
end;

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Einstellungsverwaltung in der Applikation

Beitrag von braunbär »

Stimmt, indem man den record direkt definiert und nicht über eine vorangehende Typdeklaration, bekommt man das Singleton sogar wasserdicht. :D

In meiner Firma haben wir gewohnheitsmäßig alle records nicht direkt, sonder immer via type deklariert. Probleme haben sich aber daraus in den vielen Jahren auch kein einziges mal ergeben.

Antworten