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