Circular Unit Reference - Typesafety

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Knittel
Beiträge: 12
Registriert: Sa 26. Mär 2016, 14:43

Circular Unit Reference - Typesafety

Beitrag von Knittel »

Hallo allerseits,

Ich habe im Moment eine zirkuläre Unit Referenz in meinem Programm, mir ist schon allgemein klar wie ich die behebe, aber ich frage mich ob es eine schöne Möglichkeit diese zu beheben, die auch noch typ-sicher ist.
Hier mal vereinfacht die beiden Klassen:

Code: Alles auswählen

unit SPBuff
uses SPPlayer;
TTemporaryBuff = class
  [...]
  // Wendet einen temporaeren Effekt auf einen Spieler an.
  procedure Apply(APlayer: TPlayer);

Code: Alles auswählen

unit SPPlayer
uses SPBuff;
TPlayer = class
  FListOfActiveBuffs: TTemporaryBuffList;
  function getBuff(AIndex: integer): TTemporaryBuff;
  [...]


Gibt es hierfür eine schöne Lösung, bei der ich nicht einfach eins von beiden durch TObject ersetze und einfach bei den Prozeduren in die erste Zeile ein assert(AObject is TPlayer) packe?
Ich fände es nämlich schön, wenn der Compiler die Typen irgendwie überprüfen könnte (am besten schon zur Compilezeit, asserts sind ja erst zur Laufzeit).

Vielen Dank schonmal im Vorraus,
Gruß Knittel

Jole
Beiträge: 114
Registriert: Fr 4. Jul 2014, 14:39
OS, Lazarus, FPC: Linux
CPU-Target: amd64

Re: Circular Unit Reference - Typesafety

Beitrag von Jole »

Mich würde mal interessieren was dieser Fehler mit den Prozeduren zu tun hat? Der Fehler entsteht doch weil sich die Units gegenseitig aufrufen, da ist es erst mal egal was da drin steht.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Circular Unit Reference - Typesafety

Beitrag von Socke »

Hallo Knittel,

du kannst
  • Für jede Klasse eine abstrakte Basis-Klasse in eine eigene Unit auslageren.
  • Beide Klassen in einer Unit zusammenfassen
  • Mit Interfaces arbeiten (ähnlich dem ersten Vorschlag)
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
theo
Beiträge: 10497
Registriert: Mo 11. Sep 2006, 19:01

Re: Circular Unit Reference - Typesafety

Beitrag von theo »

Klassen die so eng verflochten sind, kommen in Pascal üblicherweise in die gleiche Unit.
Wenn du unbedingt die Dateien splitten willst, kannst du auch Include Dateien machen. Lazarus kann damit umgehen.

Jole
Beiträge: 114
Registriert: Fr 4. Jul 2014, 14:39
OS, Lazarus, FPC: Linux
CPU-Target: amd64

Re: Circular Unit Reference - Typesafety

Beitrag von Jole »

Socke hat geschrieben:du kannst
Für jede Klasse eine abstrakte Basis-Klasse in eine eigene Unit auslageren.

Damit würde genau der gleiche Fehler wieder auftreten, denn das Problem mit der gegenseitigen Abhängigkeit der Units ist damit nicht gelöst.
Socke hat geschrieben: Beide Klassen in einer Unit zusammenfassen

Das ist „eine“ richtige Lösung, entspricht auch dem was theo angedeutet hat:
theo hat geschrieben:Klassen die so eng verflochten sind, kommen in Pascal üblicherweise in die gleiche Unit.

Nochmal… Units die sich gegenseitig aufrufen kann der Compiler gar nicht übersetzen, wie denn auch ? Wenn der Compiler Unit A übersetzt die mit Uses die Unit B einbindet, wird der Compiler erst die Unit B Combilieren wollen. Wenn aber Unit B nun Unit A in der uses hat, was passiert dann wohl?
Also ich würde das auch in eine Unit Packen.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2640
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Circular Unit Reference - Typesafety

Beitrag von m.fuchs »

Das Aufrufen ist nicht das Entscheidende, problematisch sind die zirkulären Deklarationen. Und die werden ja durch Auslagerung des Interfaces in eine dritte Unit aufgelöst.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: Circular Unit Reference - Typesafety

Beitrag von Euklid »

Ich habe da eine passende Frage, die wohl eher bei den Einsteigerfragen einzuordnen ist:

Welchen Unterschied macht eigentlich

Code: Alles auswählen

interface
uses unit3;


und

Code: Alles auswählen

implementation
uses unit3;


?

Ich habe die Erfahrung gemacht, dass man so zumindest scheinbar zirkulär die Units einbinden kann - indem man in der Unit 1 im Implementation-Teil auf Unit 2 verweist und in Unit 2 im Interface-Teil auf Unit 1 verweist...

Jole
Beiträge: 114
Registriert: Fr 4. Jul 2014, 14:39
OS, Lazarus, FPC: Linux
CPU-Target: amd64

Re: Circular Unit Reference - Typesafety

Beitrag von Jole »

m.fuchs hat geschrieben:Das Aufrufen ist nicht das Entscheidende, problematisch sind die zirkulären Deklarationen. Und die werden ja durch Auslagerung des Interfaces in eine dritte Unit aufgelöst.

Kannst du da mal ein Beispiel Posten? Mir ist nämlich nicht klar, wie du mit einer Dritten Unit die Abhängigkeit im Interface auflösen willst.
Hier möchte ich den zweiten Satz von theo Zitieren:
theo hat geschrieben:Wenn du unbedingt die Dateien splitten willst, kannst du auch Include Dateien machen. Lazarus kann damit umgehen.

Nach meiner Vorstellung entspricht das folgender Vorgehensweise:

Code: Alles auswählen

 
unit MyPlayer;
Interface
 
// alles deklarieren was sichtbar sein soll
Implementation
// weitere Deklarationen, falls erforderlich…
// und dann die SPBuff und SPPlayer als Include Dateien einbinden
{$i spbuff.inc}
{$i spplayer.inc}
 

Ich würde dennoch alles in eine einzige Unit packen.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2640
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Circular Unit Reference - Typesafety

Beitrag von m.fuchs »

Jole hat geschrieben:
m.fuchs hat geschrieben:Das Aufrufen ist nicht das Entscheidende, problematisch sind die zirkulären Deklarationen. Und die werden ja durch Auslagerung des Interfaces in eine dritte Unit aufgelöst.

Kannst du da mal ein Beispiel Posten? Mir ist nämlich nicht klar, wie du mit einer Dritten Unit die Abhängigkeit im Interface auflösen willst.


Klaro.

Drei Units, ein Programm.

ServiceDefinitions.pas

Code: Alles auswählen

unit ServiceDefinitions;
 
{$mode objfpc}{$H+}
{$INTERFACES Corba}
 
interface
 
uses
  Classes, SysUtils;
 
type
  IService1 = interface
    procedure WriteHello;
  end;
 
  IService2 = interface
    procedure WriteGoodbye;
  end;
 
implementation
 
end.



Service1.pas

Code: Alles auswählen

unit Service1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, ServiceDefinitions;
 
type
  TService1 = class(TObject, IService1)
    public
      procedure WriteHello;
      procedure WriteHelloAndGoodbye(Service: IService2);
  end;
 
 
implementation
 
procedure TService1.WriteHello;
begin
  WriteLn('Hello...');
end;
 
procedure TService1.WriteHelloAndGoodbye(Service: IService2);
begin
  WriteHello;
  Service.WriteGoodbye;
end;
 
end.



Service2.pas

Code: Alles auswählen

unit Service2;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, ServiceDefinitions;
 
type
  TService2 = class(TObject, IService2)
    public
      procedure WriteGoodbye;
      procedure WriteHelloAndGoodbye(Service: IService1);
  end;
 
 
implementation
 
 
procedure TService2.WriteGoodbye;
begin
  WriteLn('...Goodbey');
end;
 
procedure TService2.WriteHelloAndGoodbye(Service: IService1);
begin
  Service.WriteHello;
  WriteGoodbye;
end;
 
end.



project1.lpr

Code: Alles auswählen

program project1;
 
{$mode objfpc}{$H+}
 
uses
  Classes, SysUtils, ServiceDefinitions, Service1, Service2;
 
 
var
  a: TService1;
  b: TService2;
 
begin
  try
    a := TService1.Create;
    b := TService2.Create;
 
    a.WriteHelloAndGoodbye(b);
    b.WriteHelloAndGoodbye(a);
  finally
    FreeAndNil(b);
    FreeAndNil(a);
  end;
end.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Jole
Beiträge: 114
Registriert: Fr 4. Jul 2014, 14:39
OS, Lazarus, FPC: Linux
CPU-Target: amd64

Re: Circular Unit Reference - Typesafety

Beitrag von Jole »

@m.fuchs: Vielen Dank für das Beispiel! Ich wusste gar nicht das Interface auf diese weise deklariert werden kann, für mich gab es den Interface teil immer nur als abschnitt in einer Unit. Also, in diesem Fall hab ich wieder etwas dazu Gelernt!

Knittel
Beiträge: 12
Registriert: Sa 26. Mär 2016, 14:43

Re: Circular Unit Reference - Typesafety

Beitrag von Knittel »

Hallo nochmal,

erstmal vielen Dank für die vielen Antworten! :) Mein Beispiel mit den zwei Units sollte die Sache nur etwas anschaulicher machen. In meinem Fall geht die zirkuläre Unit Referenz allerdings durch mehrere Units durch und ich finde es grundsätzlich übersichtlicher Sachen auf mehrere Dateien aufzuteilen, daher werde ich sie auf jeden Fall nicht in eine Unit packen. Die Lösung mit dem Interface gefällt mir allerdings sehr gut.

Aber das bringt mich zu einer Frage dazu, du (m.fuchs) hast bei dir {$INTERFACES Corba} als Define/Flag (ist das der richtige Begriff?) gesetzt, entfernt dies das Reference Counting für Interfaces, weil das birgt ja mehr Gefahren als Hilfen, was ich bisher so darüber gelesen habe. Daher hab ich interfaces bisher auch gemieden.
Ansonsten müsste das doch auch äquivalent mit einer abstrakten Basis Klasse funktionieren?

Gruß Knittel

P.S. @Jole, vielleicht hab ich meine Frage etwas schlecht formuliert, die Frage war ob ich die beiden Prozeduren in zwei unterschiedliche Dateien packen kann, ohne einen solchen Fehler zu produzieren.

EDIT: @Euklid: Pascal Units sind ja in 2 Bereiche aufgeteilt. Wenn du eine Unit im interface einbindest (also oben), kannst du die eingebundene Unit in deiner gesamten Unit benutzen. Hierbei sind die zirkulären uses von Units nicht erlaubt. Wenn du im Implementation Bereich (unten) eine Unit einbindest, kannst du die Klassen/Prozeduren/... auch nur im Implementationsbereich verwenden. Für die gibt es keine Einschränkung an units die du einbinden darfst.
Nehmen wir an du hast eine Unit mit einer Klasse TAuto und einer Funktion summeVonDrei(A, B, C: integer): integer; Wenn du nur die Funktion summeVonDrei in deinem Code irgendwo benötigst, würdest du sie im implementation Teil einbinden. Wenn du aber zum Beispiel eine neue Klasse TRennAuto von TAuto erben lassen willst, würdest (und musst du auch) die unit im Interface bereich bereits einbinden.
Zuletzt geändert von Knittel am Mo 16. Mai 2016, 22:42, insgesamt 1-mal geändert.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2640
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Circular Unit Reference - Typesafety

Beitrag von m.fuchs »

Knittel hat geschrieben:Aber das bringt mich zu einer Frage dazu, du (m.fuchs) hast bei dir {$INTERFACES Corba} als Define/Flag (ist das der richtige Begriff?) gesetzt, entfernt dies das Reference Counting für Interfaces, weil das birgt ja mehr Gefahren als Hilfen, was ich bisher so darüber gelesen habe. Daher hab ich interfaces bisher auch gemieden.

Genau, irgendein geistesgestörter Entwickler bei Borland musste seinerzeit das Reference Counting mit Interfaces verbinden. Vermutlich weil die drogenabhängigen Entwickler bei Microsoft COM und ActiveX erfanden und Borland das Zeug ansteuern wollte. Kurz und gut: im CORBA-Modus ist ein Interface genau das was es sein soll. Ein Vertrag, der sicherstellt dass eine Klasse bestimmte Methoden enthält. Nichts weiter.

Knittel hat geschrieben:Ansonsten müsste das doch auch äquivalent mit einer abstrakten Basis Klasse funktionieren?

Geht natürlich auch. Das Interface hat nur noch den zusätzlichen Charme, dass ich von etwas ganz anderem ableiten kann.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: Circular Unit Reference - Typesafety

Beitrag von Euklid »

Knittel hat geschrieben:EDIT: @Euklid: Pascal Units sind ja in 2 Bereiche aufgeteilt. [...]


Aaah perfekt, danke! Das klingt einleuchtend. :)

Zum eigentlichen Problem nochmal: Ich löse den Fall von übergroßen Units auch meist mit Include-Dateien. In der ursprünglichen Unitdatei befindet sich dann nur noch Elementares, wie der Interfaceteil und constructor etc. Aber das ist für Dich ja nicht die Lösung, wenn ich das richtig mitbekommen habe.

Jole
Beiträge: 114
Registriert: Fr 4. Jul 2014, 14:39
OS, Lazarus, FPC: Linux
CPU-Target: amd64

Re: Circular Unit Reference - Typesafety

Beitrag von Jole »

Knittel hat geschrieben:P.S. @Jole, vielleicht hab ich meine Frage etwas schlecht formuliert, die Frage war ob ich die beiden Prozeduren in zwei unterschiedliche Dateien packen kann, ohne einen solchen Fehler zu produzieren.

Ich hab deine Frage schon richtig verstanden und ausgehend von deinem Anschauungsbeispiel sind meine Antworten (und auch Erklärungen) ja nicht falsch! Im Hinblick auf andere Hilfesuchende möchte ich nicht das dieser Eindruck entsteht. Die Lösungsvorschläge mit einer Unit b.z.w. den Include Dateien kommt für dich ja aber nicht in frage (es gibt ja noch eine 3. elegante Lösung).

Knittel
Beiträge: 12
Registriert: Sa 26. Mär 2016, 14:43

Re: Circular Unit Reference - Typesafety

Beitrag von Knittel »

Nochmals vielen Dank für die Antworten! :)

m.fuchs hat geschrieben:Genau, irgendein geistesgestörter Entwickler bei Borland musste seinerzeit das Reference Counting mit Interfaces verbinden. Vermutlich weil die drogenabhängigen Entwickler bei Microsoft COM und ActiveX erfanden und Borland das Zeug ansteuern wollte. Kurz und gut: im CORBA-Modus ist ein Interface genau das was es sein soll. Ein Vertrag, der sicherstellt dass eine Klasse bestimmte Methoden enthält. Nichts weiter.

Yay, das freut mich gerade richtig, dass es doch vernünftige Interfaces gibt! :)

Euklid hat geschrieben:Zum eigentlichen Problem nochmal: Ich löse den Fall von übergroßen Units auch meist mit Include-Dateien. In der ursprünglichen Unitdatei befindet sich dann nur noch Elementares, wie der Interfaceteil und constructor etc. Aber das ist für Dich ja nicht die Lösung, wenn ich das richtig mitbekommen habe.


Jole hat geschrieben:Ich hab deine Frage schon richtig verstanden und ausgehend von deinem Anschauungsbeispiel sind meine Antworten (und auch Erklärungen) ja nicht falsch! Im Hinblick auf andere Hilfesuchende möchte ich nicht das dieser Eindruck entsteht. Die Lösungsvorschläge mit einer Unit b.z.w. den Include Dateien kommt für dich ja aber nicht in frage (es gibt ja noch eine 3. elegante Lösung).

Ich hatte mich mit meinem Kommentar auf deine erste Antwort bezogen:
Jole hat geschrieben:Mich würde mal interessieren was dieser Fehler mit den Prozeduren zu tun hat? Der Fehler entsteht doch weil sich die Units gegenseitig aufrufen, da ist es erst mal egal was da drin steht.

Daher wollte ich einfach sicherheitshalber nochmal mein Problem genauer ausformulieren (auch wenn es ja schon hier passende Antworten gab). Eure Antworten sind auf keinen Fall falsch!! Sie haben mir alle sehr geholfen. :)

Danke an alle,
Gruß Knittel

Antworten