Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Zirkuläre Referenzen (https://www.delphipraxis.net/197695-zirkulaere-referenzen.html)

tumo 28. Aug 2018 17:36

Zirkuläre Referenzen
 
Hallo, vorab: ja, ich habe schon Google und die Hauseigene Suche benutzt, bin aber nicht fündig geworden.

Ich weiß, was Zirkuläre Unit Referenzen sind, auch, dass ich reichlich viel falsch gemacht habe, wenn diese von Nöten sind, nur steh ich gerade komplett auf dem Schlauch, wie ich diese umgehen kann bzw. so umstrukturiere, dass sie nicht mehr da sind.

Folgendes: Ich habe eine Map.pas. Wie der Name schon sagt, liegt darin die Klasse TMap, die einen 2D Array of TBlock (deklariert in Blocks.pas) speichert. Daneben vegetiert noch eine Entity.pas mit dem Typ TPlayer, welcher auf die Map.pas zwecks Kollisionserkennung zugreift.
Nun möchte ich einen neuen Blocktyp einführen: Blöcke mit denen man interagieren kann. Damit der Player dies tun kann, dachte ich an eine neue Block Klass TActionBlock, welche die procedure OnAction (oder so) besitzt. Als Parameter wäre da ja ein TPlayer angemessen, damit ich weiß, welcher Player denn nun agieren möchte. Gleichzeitig dachte ich, es wäre klug, dem Player immer den gerade nutzbaren Block zu speichern (logischerweiße vom Typ TActionBlock). Da es hier sehr deutlich zu einem Kreis kommt (TPlayer <--> TActionBlock), habe ich nur die Position des Blocks gespeichert, geht ja auch.
Nur gibt es noch immer eine Referenzschleife, die ich nicht zu umgehen weiß.

TPlayer -> TMap -> TActionBlock -> TPlayer.

(Sollten Code Schnipsel erwünscht sein, würde ich diese bereitstellen)

Vielen Dank im Vorraus.

Uwe Raabe 28. Aug 2018 18:40

AW: Zirkuläre Referenzen
 
Zitat:

Zitat von tumo (Beitrag 1411830)
(Sollten Code Schnipsel erwünscht sein, würde ich diese bereitstellen)

Die (abgespeckten) Klassendeklarationen wären schon hilfreich. Dann weiß man wenigstens wovon man redet.

jaenicke 28. Aug 2018 18:51

AW: Zirkuläre Referenzen
 
Mangels Quelltext möchte ich nur einmal ein Beispiel geben wie man so etwas behandeln kann.

Nehmen wir an du hast einen TPlayer, der Zugriff auf die TMap bekommt, damit er die benachbarten Blöcke kennt. Warum muss er die kennen? Um damit zu interagieren. Nun könnte man aber eine zusätzliche Klasse TPlayerActor schaffen, die dies übernimmt. Diese bekommt von einer übergeordneten Instanz (dem Spielfeld z.B.) gesagt, dass Player XY mit Block XY interagieren möchte. Der TPlayer könnte dies z.B. per Event dem Spielfeld mitteilen.

Auf die Weise kennt das Spielfeld die Blöcke und die Player, die Player und Blöcke sich aber nicht gegenseitig.

Wie gesagt, das ist nur ein Beispiel, gefallen würde mir die Struktur auch nicht. Aber es könnte so ähnlich sein wie bei dir und dir einen Denkanstoß geben hoffe ich.

hoika 28. Aug 2018 19:41

AW: Zirkuläre Referenzen
 
Hallo,
TActionBlock darf nichts von TPlayer wissen, eher umgekehrt.

Ich weiß jetzt nicht, ob das mit dem Visitor-, oder Observer-Pattern lösbar ist.

Lösbar ist es auch jeden Fall durch Nutzung von Interfaces, dann bleibt die "zugrunde liegende" Klasse außen vor.

tumo 29. Aug 2018 20:16

AW: Zirkuläre Referenzen
 
Hallo und danke für die schnellen und vielen Antworten.

@Uwe Raabe
Delphi-Quellcode:
--------Entity.pas--------
uses Map, ...;

type
TPlayer = class(TEntity)
  private
    procedure updateCollision(AMap: TMap; newX,newY: Single); override;
  public
    NearestBlock: TBlockVector;
    constructor Create(EntityPosition: TVector; SizeH,SizeV: Single); override;
    [...]
  end;

--------Map.pas--------
uses Blocks, ...;

type
TBlocks = array of array of TBlock;

  TMap = class(TObject)
  private
    Structure: TBlocks;
  [...]
  public
  [...]
  property Grid[X,Y: Word]: TBlock read GetStructure; default;
  property Grid[APos: TBlockVector]: TBlock read GetStructure; default;
end;

--------Blocks.pas--------
uses ...;

type
TBlock = class(TObject)
[...]
end;

TActionBlock = class(TBlock)
public
procedure OnAction(APlayer: TPlayer);
end;
Ist das in etwa, was Du wolltest? Mehr? Weniger?

@jaenicke Grundsätzlich danke. Das Dilemma über Events zu lösen ist durchaus eine Variante. Ich muss zugeben mit Events zu programmieren ist mir neu (klar, Komponentenevents, ich meine hier sebstverständlich eigene). Was ich sagen will: Gibt es da eine Art Tutorialempfehlung des Chefs?

@hoika Der Idee bin ich auch sehr aufgeschlossen. Darf ich erfahren, was Visitor-/Observer-Patterns sind?
Ich habe bis dato noch nie von Interfaces gehört (außer im Unit Aufbau). Eine erst Recherche ergab nur die Tatsache, dass diese existieren und Dinge können. Auch hier die eFrage: Gibt es da etwas, was Du empfehlen kannst?

Vielen Dank im vorraus

Schokohase 29. Aug 2018 21:29

AW: Zirkuläre Referenzen
 
Entity.pas braucht
Delphi-Quellcode:
Map.TMap
.
Map.pas braucht
Delphi-Quellcode:
Block.TBlock
.
Block.pas braucht
Delphi-Quellcode:
Entity.TPlayer
.

Was dann den Kreis schließt.

Hier in diesem Falle würde ich alles in eine Unit packen, dann gibt es auch kein Problem mit der Kreis.

Uwe Raabe 29. Aug 2018 22:26

AW: Zirkuläre Referenzen
 
Hier ein möglicher Lösungsvorschlag zum Aufbrechen des Kreises, ganz objektorientiert und ohne Verwendung von Interfaces (was sicher auch eine valide Lösung wäre). Statt direkt auf eine TPlayer-Instanz zuzugreifen, wird hier eine abstrakte Player-Klasse eingeführt.
Delphi-Quellcode:
unit Blocks;

interface

type
  TBlock = class(TObject)
  end;

  TAbstractPlayer = class abstract
  public
    procedure DoWithBlock(ABlock: TBlock); virtual; abstract;
  end;

  TActionBlock = class(TBlock)
  public
    procedure OnAction(APlayer: TAbstractPlayer);
  end;

implementation

procedure TActionBlock.OnAction(APlayer: TAbstractPlayer);
begin
  APlayer.DoWithBlock(Self);
end;

end.
Die Maps-Unit ist hier nicht relevant, da sie nur indirekt an dem Kreis beteiligt war und nicht selber einen gebildet hat.

Eigentlich würde man TPlayer direkt von TAbstractPlayer ableiten. Da aber TPlayer schon von TEntity abgeleitet wird, schalten wir eine neue Adapter-Klasse dazwischen.
Delphi-Quellcode:
unit Entity;

interface

uses
  Map, Blocks;

type
  TEntity = class
  end;

type
  TPlayer = class(TEntity)
  private
    FPlayerAdapter: TAbstractPlayer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure DoWithBlock(ABlock: TBlock);
  end;

implementation

type
  TPlayerAdapter = class(TAbstractPlayer)
  private
    FPlayer: TPlayer;
  public
    constructor Create(APlayer: TPlayer);
    procedure DoWithBlock(ABlock: TBlock); override;
  end;

constructor TPlayerAdapter.Create(APlayer: TPlayer);
begin
  inherited Create;
  FPlayer := APlayer;
end;

procedure TPlayerAdapter.DoWithBlock(ABlock: TBlock);
begin
  FPlayer.DoWithBlock(ABlock);
end;

constructor TPlayer.Create;
begin
  inherited Create;
  FPlayerAdapter := TPlayerAdapter.Create(Self);
end;

destructor TPlayer.Destroy;
begin
  FPlayerAdapter.Free;
  inherited Destroy;
end;

procedure TPlayer.DoWithBlock(ABlock: TBlock);
begin
end;

end.
Natürlich muss das noch an die tatsächlichen Gegebenheiten angepasst werden, aber das Prinzip sollte erkennbar sein.

p80286 30. Aug 2018 07:57

AW: Zirkuläre Referenzen
 
Wenn ich mich nicht verlesen habe, verursacht tActionblock den Ärger. Also dessen Definition in den Player verschieben und es sollte funktionieren.

Gruß
K-H

tumo 30. Aug 2018 18:02

AW: Zirkuläre Referenzen
 
Hallo

@Uwe Raabe Vielen vielen Dank für die Lösung :) Als ich die gelesen habe, habe ich erst gedacht: Ne, ist nicht ganz wie ich es brauche, aber nach einigen Minuten hat sich die Erkenntnis eingestellt: Doch, genau so könnte es klappen (Bin derzeit viel beschäftigt mit Schule, deswegen nur könnte). Ein, zwei Fragen hab ich dann doch noch.
1. Was macht
Delphi-Quellcode:
class abstract
mit der Klasse?
2. Warum die Adapter-Klasse? Kann ich nicht theoretisch einfach
Delphi-Quellcode:
class(TEntity, TAbstractPlayer)
machen?

Uwe Raabe 30. Aug 2018 18:57

AW: Zirkuläre Referenzen
 
Zitat:

Zitat von tumo (Beitrag 1412047)
1. Was macht
Delphi-Quellcode:
class abstract
mit der Klasse?

Ist eigentlich nur ein Hinweis für den Entwickler. Aktuell hat das noch keine konkreten Auswirkungen. Das Vorhandensein einer abstrakten Methode genügt bereits, daß eine Klasse abstrakt ist.


Zitat:

Zitat von tumo (Beitrag 1412047)
2. Warum die Adapter-Klasse? Kann ich nicht theoretisch einfach
Delphi-Quellcode:
class(TEntity, TAbstractPlayer)
machen?

Weil Klassen in Delphi nur einen Vorfahren haben dürfen.

tumo 31. Aug 2018 21:40

AW: Zirkuläre Referenzen
 
Hallo,

@Uwe Raabe Noch eine (vorerst) letzte Frage: Da TPlayer im interface Teil ist, ist es aus anderen Klassen aufrufbar. Nun besitzt TPlayer die Prozedur DoBlock, welche jedoch leer ist. Steht da ein höheres Verhalten hinter? Da TAdapterPlayer ja auch diese besitzt und gleichzeitig ein fPlayer hat, würde ich ja davon ausgehen, dass beim Aufruf von TPlayer.DoBlock einfach TAdapterPlayer.DoBlock aufgerufen wird. Erschließen tu sich mir die ganze Sache noch nicht...

EDIT

Sekunde mal. Kann es sein, dass TPlayer einen TAdapter besitzen soll, TAdapter aber keinen TPlayer? macht ja eingentlich auch garkeinen Sinn, oder übersehe ich da was. Die Hierarchie ist mir nähmlich ein wenig unschlüssig

Uwe Raabe 31. Aug 2018 21:57

AW: Zirkuläre Referenzen
 
Zitat:

Zitat von tumo (Beitrag 1412150)
Erschließen tu sich mir die ganze Sache noch nicht...

Nur um das richtig einzuordnen: Wie lange programmierst du schon in Delphi?

tumo 31. Aug 2018 22:02

AW: Zirkuläre Referenzen
 
Je nachdem ab wann Programmieren als dieses zählt. Mit Delphi gespielt schon sehr lange, ernsthaft programmiert seit 2-3 Jahren.

Im nachhinein kann man meinen EDIT überlesen, ich sollte endlich anfangen öfter zu lesen und dann erst zu fragen.

Uwe Raabe 31. Aug 2018 22:39

AW: Zirkuläre Referenzen
 
Will ein TPlayer bei einem TActionBlock das OnAction aufrufen, muss er einen TAbstractPlayer übergeben. Das ein TPlayer aber nicht von TAbstractPlayer abgeleitet ist, springt hier die TPlayerAdapter-Instanz ein, die alle Aufrufe von DoWithBlock einfach an den TPlayer weiterleitet.

Das ganze Code-Beispiel ist natürlich vollkommen sinnlos, da hier eigentlich nichts passiert. Es ist auch nur als Darstellung der Klassenhierarchie zum Auflösen der zirkulären Referenz gedacht. Aus deinem Beispiel geht aber auch nicht hervor, was eigentlich passieren soll und ob der Kreis nicht auch anders aufgebrochen werden kann.

tumo 1. Sep 2018 08:07

AW: Zirkuläre Referenzen
 
Hallo,

aah, verstanden. Also übergebe ich beim Aufruf von Block.OnAction den PlayerAdapter, welcher wiederrum (derzeit) einfach nur das Event zurück gibt. Dass der Code nicht viel macht ist klar, am Ende sollen damit Blöcke entstehen können, die z.B.: Den Zustand von Player properties ändern, GUIs aufrufen (für den Player) oder andere Blöcke verändern.

Dann hab ich ja jetzt alles was ich brauch. Vielen vielen Dank nochmal.


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:06 Uhr.

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