Delphi-PRAXiS
Seite 2 von 3     12 3      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi globale Variablen (https://www.delphipraxis.net/129240-globale-variablen.html)

mkinzler 15. Feb 2009 13:42

Re: globale Variablen
 
Dann hast du mehrere gloable Variablen durch eine ersetzt, aber hast trotzdem noch eine globale Variable :zwinker:

Sir Rufo 15. Feb 2009 13:43

Re: globale Variablen
 
logisch, ich brauche einen gemeinsamen Aufhänger ... sonst funzt das ja nicht.

BTW: Jedes erstellte Form ist als globale Variable definiert ... ;)

cu

Oliver

Tharon 15. Feb 2009 15:47

Re: globale Variablen
 
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

Cyf 15. Feb 2009 15:58

Re: globale Variablen
 
Warum eine Instanz statt Klassenvariablen verwenden? Und warum überhaupt eine Klasse (außer man möchte natürlich einen Zugriffsschutz über Properties), eine einzige Unit mit allen globalen Variablen tut es imho auch, solange man die Variablen immer mit Unitnamen anspricht und ob es nun Klasse.X oder Unit.X ist, spielt auch keine Rolle.

mkinzler 15. Feb 2009 16:01

Re: globale Variablen
 
Da (Object-)Pascal eine Hybridsprache ist, welche aus einer prozeduralen Sprache entstanden ist, ist es nicht möglich auf gloable Variablen zu verzichten.

Reinhard Kern 16. Feb 2009 11:16

Re: globale Variablen
 
Zitat:

Zitat von mkinzler
Da (Object-)Pascal eine Hybridsprache ist, welche aus einer prozeduralen Sprache entstanden ist, ist es nicht möglich auf gloable Variablen zu verzichten.

Hallo,

ich finde es auch überhaupt nicht sinnvoll: wenn ich z.B. ein Programm zur Dateibearbeitung habe, das genau 1 Datei zur Zeit bearbeitet, dann SIND z.B. die Datei und der Dateiname globale Variablen, und solange ich beim Programmieren des Denkens noch mächtig bin, sehe ich auch garkeinen Grund, diese Tatsache mit fragwürdigen OOP-Konstrukten zu verstecken, nur um das Dogma "niemals global" einzuhalten. Delphi selbst definiert wichtige globale Variablen, z. B. Application.

Nachteilig ist nur, dass Borland es versäumt hat, einen echten globalen Namespace zu schaffen (z.B. durch eine var-Deklaration im dpr-file, oder meinetwegen auch mit "global"), so dass man nicht einfach mit Bordmitteln auf Mehrfachinstanziierung prüfen kann (oder diese von vornherein verhindert wird).

Gruss Reinhard

p80286 16. Feb 2009 12:54

Re: globale Variablen
 
Hallo zusammen,

wenn ich mich richtig erinnere sind "globale" Variablen PfuiBa weil zu viele Nebeneffekte auftreten können z.B. bei Laufvariablen wie i,j,k.....
Grundsätzlich sollten alle Variablen nur innerhalb einer Routine gültig sein. Und die Übergabe nur über die "offizielle" Schnittstelle erfolgen.
Meiner Meinung nach ist es MANCHMAL sinnvoll in einer "globalen" Variablen Informationen zu hinterlegen, die in der ganzen Anwendung benötigt werden. Das diese Notwendigkeit von dem einen oder anderen cleveren Codebastler mißbraucht wurde, steht auf einem ganz anderen Blatt.
Und wenn schon global, dann sollte man(n) tunlichst vermeiden wild aus einer Subroutine heraus darauf zuzugreifen. (Auch wenn es im Prinzip möglich wäre)

Gruß K-H

Pfoto 16. Feb 2009 14:50

Re: globale Variablen
 
Zitat:

Zitat von Tharon
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;

[...]

Zu dem Code von Tharon hätte ich die Frage:

wie kann ich denn nun auf das einmalig instanzierte Objekte zugreifen?

Müsste "m_Instance" deshalb nicht _vor_ dem "implementation" Teil stehen oder gibt es da eine andere vorgehensweise?


Gruß
Jürgen

DeddyH 16. Feb 2009 15:01

Re: globale Variablen
 
Stimmt, die globale Variable muss in den interface-Teil, ansonsten ist sie nicht von außen erreichbar.

Hawkeye219 16. Feb 2009 15:03

Re: globale Variablen
 
Hallo,

der Zugriff erfolgt über die Klassenmethode RetrieveInstance, die eine Referenz auf die (einzige) Instanz liefert:

Delphi-Quellcode:
uses
  GlobalRessources;

begin
  TGlobalRessources.RetrieveInstance.<Eigenschaft>
end;
Gruß Hawkeye


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:46 Uhr.
Seite 2 von 3     12 3      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz