Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Architektur / Projektstruktur / zirkuläre Referenz Problem (https://www.delphipraxis.net/165397-architektur-projektstruktur-zirkulaere-referenz-problem.html)

_BlackDragon_ 28. Dez 2011 14:48

Delphi-Version: 2006

Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Hi Leute.

Suche nun schon seit Stunden für einen Lösungsansatz, leider bisher ohne wirklichen Erfolg, bzw. ohne einen Ansatz der mir so richtig gefällt.

Und zwar möchte ich eine Struktur aufbauen, welche eine noch undefinierte Anzahl an Klassen enthält. Diese würde ich gerne, der Übersicht wegen, in separate Units auslagern. Nun gibt es aber das Problem, das diese sich gegenseitig "kennen" sollen, wodurch aber das Problem von zirkulären Referenzen entstehen würde.

Als Beispiel ziehe mal folgendes Szenario heran. Es existiert eine Klasse TEngine und es existieren mehrere Managerklassen. Nun soll natürlich die Klasse TEngine auch auf die einzelnen Manager zugreifen können und diese sollen natürlich auch die Klasse TEngine kennen um darauf zugreifen zu können.

Delphi-Quellcode:
unit Engine;

interface

uses Manager1; // geht ja nicht

type
  TEngine = class(TObject)
  private
    FManager1: TManager1;
  protected
  public
    property Manager1: TManager1 read FManager1;
  end;
Delphi-Quellcode:
unit Manager1;

interface

uses Engine; // geht ja nicht

type
  TManager1 = class(TObject)
  private
    FEngine: TEngine;
  protected
  public
    constructor Create(e: TEngine);
  end;

...

implementation

TManager1.Create(e: TEngine);
begin
  FEngine := e;
end;
Freue mich auf eure Antworten und Vorschläge.

Greets

Bummi 28. Dez 2011 15:03

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
forward Deklarationen über mehrere Units sind AFAIK nicht möglich.

_BlackDragon_ 28. Dez 2011 15:14

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Genau dort liegt mein Problem. Das wäre auch zu schön gewesen, einfach eine Unit in der alle Klassen vordeklariert sind, um diese dann einfach einbinden zu können. :-D

Nichts desto trotz muss es doch irgendeine Möglichkeit geben, eine vernünftige (ansichtssache :-D) Objektstruktur über mehrere Units zu verteilen. :?

Tobinator 28. Dez 2011 15:17

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Ich bin mir nicht ganz sicher, aber hast du schonmal über Polymorphie nachgedacht?

Also das du in einer Unit deine TEngine-Klasse mit abstrakten methoden definierst, diese dann in den Manager-Units einbindest und zum schluss noch in die konkrete TEngine-Unit deine Managerunit einbindest?

neo4a 28. Dez 2011 15:23

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Du verwendest D2006, so dass ich mir den Hinweis auf das Spring-DI-Framework sparen kann.

Aber natürlich steht Dir mit den Interfaces ein Konzept zur Verfügung, mit dem Du schön modularisieren kannst, ohne Dich mit zirkulären Referenzen plagen zu müssen.

Delphi-Quellcode:
unit uInterfaces
interface

uses
  Engine, Manager1;

type
  IManager1 = interface
    [Shift+Strg+G]
  end;

  IEngine = interface
    [Shift+Strg+G]
    function GetManager1 : IManager1;
    property Manager1: IManager1 read GetManager1;
  end;
Delphi-Quellcode:
unit Engine;
interface
implementation
uses
  uInterfaces;

type
  TEngine = class(TInterfacedObject, IEngine)
  private
    FManager1: IManager1;
    function GetManager1 : IManager1;
  end;
Delphi-Quellcode:
unit Manager1;
interface
implementation
uses
  uInterfaces;

type
  TManager1 = class(TInterfacedObject, IManager1)
  private
    FEngine: IEngine;
  protected
  public
    constructor Create(e: IEngine);
  end;

_BlackDragon_ 28. Dez 2011 15:32

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Erstmal ein Dankeschön für alle bisherigen Antworten.

Habe beide Möglichkeiten bereits in Betracht gezogen und wäge gerade die Vor- und Nachteile ab.

Mal abgesehen von etwas mehr Aufwand bei der Implementierung, halte ich das Konzept mit den Interfaces für die bessere Variante und werde mich damit auseinandersetzen.

Trotzdem bin ich für weitere Vorschläge offen. :-D

neo4a 28. Dez 2011 15:32

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Zitat:

Zitat von _BlackDragon_ (Beitrag 1143483)
Das wäre auch zu schön gewesen, einfach eine Unit in der alle Klassen vordeklariert sind, um diese dann einfach einbinden zu können.

Nimm aus meinem Beispiel die Unit
Delphi-Quellcode:
uInterfaces
und Du hast genau das.

Zu bemerken ist, dass die Klassen TEngine und TManager1 bei dieser Konstruktion praktisch nichts im Interface-Teil stehen haben und es nur eine Use-Klausel gibt, nach der man auch ohne Tools gar nicht lange suchen muss - SCNR.

_BlackDragon_ 28. Dez 2011 16:17

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Ok, das Prinzip via Interfaces hab ich im Ansatz verstanden und werde mein Wissen diesbezüglich noch erweitern.

Was mir allerdings nicht ganz klar ist, wieso ich in die Interface-Unit die "Objekt"-Units einbinden "muss". Ich habe sie mal zum Testen auskommentiert und die Klassendeklarationen (TEngine und TManager1) in den Interface-Teil der Units verschoben. So ist deren Sichtbarkeit gewährleistet. Oder blick ich grad nicht durch? :?

Habe die entsprechenden Stellen hier nur auskommentiert.

Delphi-Quellcode:
unit uInterfaces
interface

//uses
//  Engine, Manager1;

type
  IManager1 = interface
    [Shift+Strg+G]
  end;

  IEngine = interface
    [Shift+Strg+G]
    function GetManager1 : IManager1;
    property Manager1: IManager1 read GetManager1;
  end;
Delphi-Quellcode:
unit Engine;
interface
//implementation
uses
  uInterfaces;

type
  TEngine = class(TInterfacedObject, IEngine)
  private
    FManager1: IManager1;
    function GetManager1 : IManager1;
  end;

implementation
...
Delphi-Quellcode:
unit Manager1;
interface
//implementation
uses
  uInterfaces;

type
  TManager1 = class(TInterfacedObject, IManager1)
  private
    FEngine: IEngine;
  protected
  public
    constructor Create(e: IEngine);
  end;

implementation
...
Sollte doch auch so funktionieren. Oder übersehe ich etwas?

neo4a 28. Dez 2011 16:26

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Zitat:

Zitat von _BlackDragon_ (Beitrag 1143506)
Was mir allerdings nicht ganz klar ist, wieso ich in die Interface-Unit die "Objekt"-Units einbinden "muss".

Sorry dafür, das ist bei diesem Beispiel natürlich nicht nötig. Es ist mir "reingerutscht", weil ich eigentlich die Interface-Deklarationen in den Units der Klassen belasse und in uInterfaces lediglich schreibe:

Delphi-Quellcode:
uses
  uEngine, uManager1;
type
  IEngine = uEngine.IEngine;
  IManager1 = uManager1.IEngine;
Diese Struktur ist bei vielen und/oder umfangreichen Interfaces viel übersichtlicher.

Und widerstehe bei diesem Konzept der Versuchung, die Deklaration der Klassen in den Interface-Teil zu ziehen. Du machst alles richtig, wenn sie isoliert in der Implementation-Sektion verbleiben. Für das Interface nach "draußen" gibt es ja ... das Interface. ;)

_BlackDragon_ 28. Dez 2011 16:35

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Zitat:

Zitat von neo4a (Beitrag 1143507)
Sorry dafür,...

Ist nicht zu entschuldigen! :-D

Ich hab wohl eher für den Stoß in die richtige Richtung zu danken. Nun heißt es lesen, lernen und loslegen. :wink:

stahli 28. Dez 2011 16:47

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Ich hatte das Problem/Thema auch schon einmal angesprochen: http://www.delphipraxis.net/1060983-post3.html

Bisher habe ich Interfaces immer vermieden und lieber "richtige Objekte" verwendet. Der Mehraufwand, extra Interfaces zu definieren, hat sich für mich nicht gerechnet.

Da ich inzwischen aber auch stärker auf Trennung von Projektteilen achte, scheint das Konzept von neo4a aber doich nicht uninteressant.
Mal sehen, vielleicht im nächsten Projekt... ;-)

neo4a 28. Dez 2011 16:52

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Ich möchte Dir noch bei Deinen Untersuchungen einen Tipp resp. Warnung mitgeben: Die Arbeit mit Interfaces kann sich zu einem Albtraum entwickeln, weil je nach Implementierung die Freigabe der Objekte variiert und automatisch durch Delphi vorgenommen werden kann.

Hier hilft Dir ein Framework wie das schon erwähnte Delphi-Spring und kann Dir in der Folge sehr viel Frust abnehmen. Auf jeden Fall solltest Du in deiner dpr aktivieren:

Delphi-Quellcode:
begin
  ReportMemoryLeaksOnShutdown := DebugHook <> 0;
  Application.Initialize;

neo4a 28. Dez 2011 17:07

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Zitat:

Zitat von stahli (Beitrag 1143510)
das Konzept von neo4a

Es ist ja nicht so sehr meins, aber es ist ein Konzept, das in die Software-Entwicklung das einbringt, was in der Industrie längst praktiziert wird: standardisierte Schnittstellen.

Für Interfaces wird häufig der Vergleich zu Lampen in Häusern bemüht: Solange Du Deine Lampen nur einmalig installierst, kannst Du sie direkt ans Stromnetz anschließen (wird ja so bei den Deckenlampen gemacht). Portable Lampen haben einen Stecker und der Stecker passt in die Dose (der E-Dienst), der es egal ist, ob da eine Lampe, ein Toaster .... Solange der Stecker passt gibt's Strom, egal, was das Gerät damit macht.

Genauso kann man die Aufgabe von Interfaces auffassen: Sie verbergen den zu verbindenen Software-Teilen einander die jeweilge Funktionalität. Damit wird Dein Code "industrialisiert", d.h. modular, wartbar, testbar. Die Künstler unter uns werden nun weinen, aber das ist dann halt so.

Furtbichler 28. Dez 2011 18:24

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
...nur hat die Industrie keine Probleme mit Speicherlecks.

Meine Grundregel: Entweder die Objekte NIE oder IMMER als Interface ansprechen. Dann gibts keine Probleme mit der Referenzzählung.

neo4a 28. Dez 2011 18:52

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Zitat:

Zitat von Furtbichler (Beitrag 1143524)
...nur hat die Industrie keine Probleme mit Speicherlecks.

Davon einmal abgesehen, kann man von der Standardisierung in der Industrie eben auch lernen, was es heißt, sich Konventionen zu unterwerfen, um im Geschäft zu bleiben.

Zitat:

Zitat von Furtbichler (Beitrag 1143524)
Meine Grundregel: Entweder die Objekte NIE oder IMMER als Interface ansprechen. Dann gibts keine Probleme mit der Referenzzählung.

Als Erweiterung dieser Grundregel würde ich auch empfehlen, jede Klasse, die ein Interface implementiert, auch nur in der Implementation-Sektion der Unit zu deklarieren. Ist sicher sicherer.

Furtbichler 28. Dez 2011 20:02

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Zitat:

Zitat von neo4a (Beitrag 1143527)
...Klasse, die ein Interface implementiert, auch nur in der Implementation-Sektion der Unit zu deklarieren. Ist sicher sicherer.

Das ist aber blöd. Interfaces nur für private Klassen oder hab ich da was verpeilt? :stupid:

_BlackDragon_ 29. Dez 2011 13:12

AW: Architektur / Projektstruktur / zirkuläre Referenz Problem
 
Nochmal ein Danke an alle die hier bisher geantwortet haben. Allerdings hab ich nun ein Verständnisproblem in Bezug auf Vererbung. Normalerweise würde ich dafür einen neuen Thread eröffnen, aber da die Erklärung des Problems auf der bereits am Anfang gelisteten Erklärung basiert, packe ich das mal hier mit hinzu.

Und zwar existiert ja wie gesagt ein Engine-Objekt und (später) mehrere Manager. Diese sollen von einer Basisklasse abgeleitet werden. Nun ergibt sich grob zusammengefasst folgende Konstruktion.

Delphi-Quellcode:
unit ICE_Interfaces;

interface

const
  IID_IICEEngine: TGUID = '{1E017D9B-7CA7-4D5E-A124-09A11C38956C}';
  IID_IICEEngineObject: TGUID = '{4B79DEA1-B898-4604-8F34-BC7F9D9575CA}';
  IID_IICEResourceManager: TGUID = '{B773EEA0-5055-4519-8D91-BAAD290712BD}';

{ Forward. }

type
  IICEEngine = interface;
  IICEEngineObject = interface;
  IICEResourceManager = interface;

  IICEEngine = interface(IUnknown)
    ['{1E017D9B-7CA7-4D5E-A124-09A11C38956C}']
    function GetResourceManager: IICEResourceManager; safecall;
    property ResourceManager: IICEResourceManager read GetResourceManager;
  end;

  IICEEngineObject = interface(IUnknown)
    ['{4B79DEA1-B898-4604-8F34-BC7F9D9575CA}']
    function GetEngine: IICEEngine; safecall;
    property Engine: IICEEngine read GetEngine;
  end;

  IICEResourceManager = interface(IICEEngineObject)
    ['{B773EEA0-5055-4519-8D91-BAAD290712BD}']
  end;

implementation

end.
Delphi-Quellcode:
unit ICE_BaseClasses;

interface

uses
  ICE_Interfaces;

type
  TICEEngineObject = class(TInterfacedObject, IICEEngineObject)
  private
    FEngine: IICEEngine;
    function GetEngine: IICEEngine;
  protected
    function _GetEngine: IICEEngine; safecall;

    { IICEEngineObject }

    function IICEEngineObject.GetEngine = _GetEngine;
  public
    constructor Create(e: IICEEngine); virtual;

    property Engine: IICEEngine read GetEngine;
  end;

implementation

{ TICEEngineObject }

constructor TICEEngineObject.Create(e: IICEEngine);
begin
  FEngine := e;
end;

function TICEEngineObject.GetEngine: IICEEngine;
begin
  Result := FEngine;
end;

function TICEEngineObject._GetEngine: IICEEngine;
begin
  Result := Engine;
end;

end.
Delphi-Quellcode:
unit ICE_ResourceManager;

interface

uses
  Classes, SysUtils,

  ICE_BaseClasses, ICE_Interfaces;

type
  TICEResourceManager = class(TICEEngineObject,IICEResourceManager)
  private

  protected

  public
    constructor Create(e: IICEEngine); override;

  end;


implementation

{ TICEResourceManager }

constructor TICEResourceManager.Create(e: IICEEngine);
begin
  inherited Create(e);
end;

initialization

finalization

end.
IICEResourceManager erbt von IICEEngineObject.
Genaue wie TICEResourceManager von TICEEngineObject.

Code:
TICEEngineObject -> implementiert -> IICEEngineObject
     |                                        |
  Ableitung                              Ableitung
     |                                        |
TICEResourceManager -> implementiert -> IICEResourceManager
Nun bekomme ich beim kompilieren folgende Fehlermeldung "[Pascal Fehler] ICE_ResourceManager.pas(40): E2003 Undefinierter Bezeichner: 'GetEngine'".

Manchmal seh ich glaube ich den Wald vor lauter Bäumen nicht. Ich kann diese Fehlermeldung in diesem Zusammenhang nicht nachvollziehen bzw. sehe keine Begründung dafür.


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:11 Uhr.

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