Delphi-PRAXiS
Seite 1 von 2  1 2      

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.


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:06 Uhr.
Seite 1 von 2  1 2      

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