Thema: Delphi globale Variablen

Einzelnen Beitrag anzeigen

Tharon

Registriert seit: 19. Okt 2004
Ort: Frankfurt/Main
12 Beiträge
 
Delphi 2007 Professional
 
#13

Re: globale Variablen

  Alt 15. Feb 2009, 15:47
Hi @all!

Dann gebe ich hier auch mal meinen Senf dazu, denn dieses Thema scheint ja immer noch viele Fragen aufzuwerfen Auch Borland macht es nicht unbedingt toll vor, weshalb man sich natürlich auch nicht wundern darf, dass gerade viele Anfänger immer wieder die gleichen Fehler machen

Also... globale Variablen? Generell nein!

Aber... wie bei jeder Regel gibt es Ausnahmen Und eigentlich ist auch die Frage nach "globalen Variablen" nicht wirklich sinnvoll gestellt, denn es kommt letztendlich darauf an, wie diese deklariert und verwendet werden und wie deren Wert gesetzt/verändert werden kann.

Der Ansatz von Sir Rufo ist ja schon ganz gut... immerhin werden hier schon mal alle globalen Variablen innerhalb einer Klasse gekapselt. Er verwendet zwar einen OOP-Ansatz, dies ist allerdings hier eher eine "Formalität", denn die eigentlichen Probleme, die globale Variablen verursachen, werden nicht gelöst:
  • Mehrfache Instanziierung: Die Klasse TGlobalData kann mehrfach instanziert werden, an beliebigen Stellen im Quellcode.
  • Keine Kontrolle über die Werte: Die Eigenschaften dieser Instanz (entsprechen den "stand alone"-Variablen) können beliebig von jedem Quelltext geändert werden.
Das erste Problem der mehrfachen Instanziierung entspricht genau dem Problem, das AlexanderBrade hatte - er hatte 2 mal die gleiche globale Variable deklariert.

Die Lösungen für diese Probleme sind recht einfach, wenn man den Ansatz von Sir Rufo konsequent weiter verfolgt.

Mehrfache Instanziierung verhindern

Die mehrfache Instanziierung muss verhindert werden - hierfür gibt es ein einfaches Design Pattern: Singleton. Dieses Pattern bewirkt, dass eine Klasse nur ein einziges mal instanziiert werden kann - und das bezeichnet man als "Singleton".
Wie mkinzler schon richtig anmerkte, gäbe es tatsächlich immer noch eine globale Variable für ein Objekt der Klasse TGlobalData, durch die Implementierung als Singleton entfällt nun aber die Notwendigkeit für eine globale Variable!
Jeder Code, der Zugriff auf die gobalen Daten benötigt, fordert über eine Klassenmethode des Singleton (z.B. RetrieveInstance) die (einzige) globale Instanz dieser Klasse an.

Ohne jetzt zu sehr auf alle Details des Singleton Patterns eingehen zu wollen (einfach mal Suchen nach Singleton): der "Trick" besteht darin, dass es keinen öffentlichen Konstruktor gibt, um die beliebige Instanziierung zu verhindern. In Delphi wird einfach der immer vorhandene öffentliche Konstruktor "deaktiviert", indem dieser eine Exception auslöst. Statt dessen implementiert man einen privaten Konstruktor, der von einer Klassenmethode RetrieveInstance aufgerufen wird. Dieser private Konstruktor ruft einfach den geerbten Konstrultor auf.
Die Klassenmethode RetrieveInstance überprüft zunächst, ob bereits eine Instanz existiert und erzeugt ggf. eine Instanz (falls noch nicht vorhanden) über den privaten Konstruktor und liefert diese zurück. Die Klassenmethode RetrieveInstance ist also der Ersatz für den öffentlichen Konstruktor und somit hat die Klasse die volle Kontrolle über ihre Instanziierung.

In meinen Projekten verwende ich eine Singleton-Klasse TGlobalRessources:

Delphi-Quellcode:
unit GlobalRessources;

...

TGlobalRessources = class
  private
    // ...
    constructor _Create; virtual;
  public
    constructor Create; virtual;
    class function RetrieveInstance(): TGlobalRessources;
    // ...
end;


implementation


var
  m_Instance: TGlobalRessources;


constructor TGlobalRessources._Create;
begin
  inherited Create;
end;


constructor TGlobalRessources.Create;
begin
  raise Exception.Create('Trying to instantiate singleton class');
end;


function TGlobalRessources.RetrieveInstance();
begin
  if (m_Instance <> nil) then
  begin
    m_Instance := _Create();
  end;

  Result := m_Instance;
end;


initialization


m_Instance := nil;

// oder, wenn sofortige Instanziierung sinnvoll ist:

m_Instance := TGlobalRessources.RetrieveInstance();


finalization


if (m_Instance <> nil) then
begin
  FreeAndNil(m_Instance);
end;
Ich hoffe, ich habe hier jetzt keine Fehler eingebaut, denn ich habe das jetzt mal eben schnell hier hin getippt, ohne in meinem Code nachzuschauen. Aber es ging mir ja auch nur darum, das Prinzip zu verdeutlichen


Kontrolle über die Variablen-Werte

Schön... jetzt hat man ein globales Objekt, das auch ganz sicher nur ein einziges mal existiert... aber trotzdem kann jetzt immer noch jeder beliebige Code willkürlich die Eigenschaften dieses Objektes (globale Daten) verändern...

Die Lösung dieses Problems ist noch einfacher und lautet: Eigenschaften und Zugriffsmethoden

Wenn man konsequent Zugriffsmethoden für alle Eigenschaften verwendet, erreicht man zumindest schon mal, dass die Wertzuweisungen (natürlich auch das Auslesen) selbst unter der Kontrolle der betreffenden Klasse stehen. Allerdings sollte man bei einer solchen Klasse noch einen Schritt weiter gehen und nach Möglichkeit nur "read only"-Eigenschaften verwenden, also Eigenschaften ohne write-Methode.

Als typisches Beispiel nehme ich mal den am Programm angemeldeten Benutzer. Man könnte nun diverse Properties für den Bennutzer deklarieren (wie UserID, UserName, UserIsAdmin, etc.). Diese Eigenschaften werden dann z.B. von einem Login-Formular gesetzt. Das Problem ist, dass diese Eigenschaften ausser vom Login-Formular auch von jedem beliebigen anderen Code gesetzt werden können und das auch noch unabhängig voneinander!
Beispielsweise könnte man auf die Idee kommen, eine Funktion zu implementieren, die aus dem Programm heraus die Möglichkeit bietet, die Anmeldung zu ändern. Der Entwickler implementiert hierzu vielleicht ein eigenes Formular und verwendet nicht das eigentliche Login-Formular. Außerdem setzt er vielleicht nur die Eigenschaft UserID und "vergisst", dass hiervon noch weitere Eigenschaften abhängen --> "bumm"

Hier sollte man also besser für den angemeldeten Benutzer ein eigenes Objekt vorsehen (bei mir vom Typ TUserProfile). Dieses Objekt UserProfile ist eine öffentliche Eigenschaft der Global Ressorce und kann nur gelesen werden.
Die oben erwähnten Eigenschaften UserID, UserName, UserIsAdmin, etc. sind nun Eigenschaften der Klasse TUserProfile und können ebenfalls nur gelesen werden. Für die Anmeldung stellt die Klasse TUserProfile eine Methode Login zur Verfügung. Ebenso eine Methode, um das Kennwort zu ändern (ChangePwd).

Damit ist folgendes sichergestellt:
  • Es gibt nur ein einziges UserProfile-Objekt, da dieses eine Eigenschaft des Singletons TGlobalRessources ist.
  • Das UserProfile-Objekt selbst kann nicht verändert werden, da es eine "read only"-Eigenschaft des TGlobalRessources-Objektes ist.
  • Die Eigenschaften des angemeldeten Benutzers (UserID, UserName, UserIsAdmin, etc.) sind immer konsistent, da diese nur intern über die Methode Login gesetzt werden.

So.. und jetzt beende ich mal meine Ausführungen... ist ja schon fast ein Tutorial geworden

Liebe Grüße
Thomas
  Mit Zitat antworten Zitat