Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Interfaces Generics (https://www.delphipraxis.net/171488-interfaces-generics.html)

Jonas Shinaniganz 8. Nov 2012 14:18

Interfaces Generics
 
Eine kurze Frage zum Thema.

Wie muss Ich meinen Code ändern damit der "Publisher" Elemente, welche das Interface "ISubscriber" implementieren, aufnimmt?


Delphi-Quellcode:
  ISubscriber = interface
    procedure GetNewspaper(Newspaper : TNewspaper);
  end;

  // Die generische Liste soll Elemente, welche das Interface implementieren, verwalten
  TPublisher = class(TObjectList<ISubscriber>)
  public
    procedure AddSubscriber(ISubscriber);
    procedure RemoveSubscriber(ISubscriber);
    procedure MessageAllSubscriber;
  end;
Ich kann die Generische Klasse aber nur mit einem konkreten Klassen-Parameter vererben oder instanzieren.

Ich will aber das da oben :( Wieso sollte meiner generischen Liste nicht das Interface genügen? Das wäre echte Kapselung.

Was kann ich machen?

mjustin 8. Nov 2012 14:35

AW: Interfaces Generics
 
Zitat:

Zitat von Jonas Shinaniganz (Beitrag 1190352)
Eine kurze Frage zum Thema.
Wie muss Ich meinen Code ändern damit der "Publisher" Elemente, welche das Interface "ISubscriber" implementieren, aufnimmt?


Delphi-Quellcode:
type
 ISubscriber = interface
    procedure GetNewspaper(Newspaper : TObject);
  end;

  // Die generische Liste soll Elemente, welche das Interface implementieren, verwalten
  TPublisher = class(TList<ISubscriber>)
  public
    procedure AddSubscriber(S: ISubscriber);
    procedure RemoveSubscriber(S: ISubscriber);
    procedure MessageAllSubscriber;
  end;

Erklärung: TList kann Schnittstellen, TObjectList nicht.

Stilistisch würde ich aber keine AddSubscriber / RemoveSubscriber Methoden deklarieren, denn wenn diese eigenen Code enthalten der in der Oberklasse Add/Remove Methode nicht enthalten ist, könnten Klienten der Klasse weiter auf Add/Remove zugreifen (absichtlich oder unabsichtlich) und damit den eigenen Code umgehen.

Jonas Shinaniganz 8. Nov 2012 14:40

AW: Interfaces Generics
 
Liegt das einfach daran, das Add und Sub aus IMath und _AddRef, _Release und QueryInterface aus IInterface dort nicht implementiert sind?

Ich wollte doch auchnoch das Typecasting umgehen. Ach Ich sehe grade, du verwendest auch die generische TList. Okay gut.

Und wenn Ich jetzt noch sowas wie OwnsObjects haben möchte? Selbst schreiben?

Bummi 8. Nov 2012 14:42

AW: Interfaces Generics
 
OwnsObjects bei interfaces ?

Jonas Shinaniganz 8. Nov 2012 14:44

AW: Interfaces Generics
 
Ja "OwnsObjects bei interfaces" . Das wäre nett.

Aber ich merke schon an der Art deines Posts, da will ich scheinbar zu viel / etwas was nicht möglich ist. Auf das ich mit nachdenken wohl auch selbst kommen sollte.

;)

Jonas Shinaniganz 8. Nov 2012 14:52

AW: Interfaces Generics
 
Zitat:

Stilistisch würde ich aber keine AddSubscriber / RemoveSubscriber Methoden deklarieren, denn wenn diese eigenen Code enthalten der in der Oberklasse Add/Remove Methode nicht enthalten ist, könnten Klienten der Klasse weiter auf Add/Remove zugreifen (absichtlich oder unabsichtlich) und damit den eigenen Code umgehen.
Ich wollte eigentlich nur bessere Namen für die Add / Remove Methoden erzielen. Vielleicht sollte Ich die TObjectList besser als Feld meiner TPublisher Klasse deklarieren, ich denke, dann trifft es eher die Art, wie ich sie mir in meinem Konzept ausgedacht habe.

Delphi-Quellcode:
TPublisher
private
  Subscribers : TList;
public
  procedure AddSubscriber(Subscriber : ISubscriber);
  procedure RemoveSubscriber(Subscriber : ISubscriber);
In dem oberen Beispiel kann Ich ja auch noch die generische Version der TList nehmen.

Stevie 8. Nov 2012 15:39

AW: Interfaces Generics
 
Wenn du im zuletzt geposteten Beispiel die Subscriber in einer TList speicherst, haust du dir u.U. den RefCount kaputt. Denn das AddSubscriber sorgt nur für eine weak Reference auf deinen Subscriber (es wird beim adden in die TList, welche ja nur Pointer aufnimmt, kein _AddRef durchgeführt). Wenn dieser Subscriber somit irgendwo out of scope läuft und (sofern du natürlich das automatische Refcounting von TInterfacedObject z.B. benutzt) freigegeben wird, hängt in deiner Subscriber Liste ein dangling Pointer und beim nächsten Zugriff darauf (z.B. durch MessageAllSubscribers) knallts.

Jonas Shinaniganz 8. Nov 2012 15:47

AW: Interfaces Generics
 
Okay ich habe scheinbar die IInterfaces noch nicht ganz verstanden.

Hier ist mein Konzept:

Delphi-Quellcode:

type
  TNewspaper = class
  private
    FTitle : String;
    procedure SetTitle(const Value : String);
  public
    property Title : String read FTitle write SetTitle;
  end;

  ISubscriber = interface
    procedure GetNewspaper(Newspaper : TNewspaper);
  end;

  TPublisher = class
  protected
    Subscribers : TList<ISubscriber>;
  public
    constructor Create;
    procedure AddSubscriber(Subscriber : ISubscriber); virtual; abstract;
    procedure RemoveSubscriber(Subscriber : ISubscriber); virtual; abstract;
    procedure SendNewsToAllSubs; virtual; abstract;
  end;
Hier sind ein paar konkrete Klassen:

Delphi-Quellcode:

type

  TFAZ = class(TPublisher)
  private
    FCurrentNewspaper : TNewspaper;
    procedure SetCurrentNewspaper(const Value: TNewspaper);
    function GetCurrentNewspaper : TNewspaper;
  public
    procedure AddSubscriber(Subscriber : ISubscriber); override;
    procedure RemoveSubscriber(Subscriber : ISubscriber); override;
    procedure SendNewspaperToAllSubs;
    property CurrentNewspaper : TNewspaper read GetCurrentNewspaper write SetCurrentNewspaper;
  end;

  TFischer = class(TInterfacedObject, ISubscriber)
    procedure GetNewspaper(Newspaper : TNewspaper);
  end;

  TMeier = class(TInterfacedObject, ISubscriber)
    procedure GetNewspaper(Newspaper : TNewspaper);
  end;

Zitat:

Wenn du im zuletzt geposteten Beispiel die Subscriber in einer TList speicherst, haust du dir u.U. den RefCount kaputt. Denn das AddSubscriber sorgt nur für eine weak Reference auf deinen Subscriber (es wird beim adden in die TList, welche ja nur Pointer aufnimmt, kein _AddRef durchgeführt). Wenn dieser Subscriber somit irgendwo out of scope läuft und (sofern du natürlich das automatische Refcounting von TInterfacedObject z.B. benutzt) freigegeben wird, hängt in deiner Subscriber Liste ein dangling Pointer und beim nächsten Zugriff darauf (z.B. durch MessageAllSubscribers) knallts.

Wie verwalte ich denn dann korrekter Weise meine Referenzen? Gibt es die Möglichkeit mir den Referenzzähler der Interface-Klasse zunutzen zu machen? Ist sowas sogar so gedacht? Wie würde ich das denn machen?

Grüße

mjustin 8. Nov 2012 16:11

AW: Interfaces Generics
 
Zitat:

Zitat von Stevie (Beitrag 1190374)
es wird beim adden in TList, welche nur Pointer aufnimmt, kein _AddRef durchgeführt

Gilt das obige denn auch für die generische TList<Interface>?

Stevie 8. Nov 2012 16:16

AW: Interfaces Generics
 
Zitat:

Zitat von mjustin (Beitrag 1190388)
Zitat:

Zitat von Stevie (Beitrag 1190374)
es wird beim adden in TList, welche nur Pointer aufnimmt, kein _AddRef durchgeführt

Gilt das obige denn auch für die generische TList<Interface>?

Nein, der Compiler sorgt dafür, dass für managed Typen der richtige Source generiert wird (im Falle von Interfaces _AddRef und _Release, bzw IntfCopy).
Daher ist auch eine TList<TFoo> und TList<IFoo> binär inkompatibel, weil für Objekte und Interfaces unterschiedlicher Code generiert wird, obwohl es naiv gesagt beides nur Listen sind, die nen Pointer speichern.


Zitat:

Zitat von Jonas Shinaniganz (Beitrag 1190377)
Zitat:

Wenn du im zuletzt geposteten Beispiel die Subscriber in einer TList speicherst, haust du dir u.U. den RefCount kaputt. Denn das AddSubscriber sorgt nur für eine weak Reference auf deinen Subscriber (es wird beim adden in die TList, welche ja nur Pointer aufnimmt, kein _AddRef durchgeführt). Wenn dieser Subscriber somit irgendwo out of scope läuft und (sofern du natürlich das automatische Refcounting von TInterfacedObject z.B. benutzt) freigegeben wird, hängt in deiner Subscriber Liste ein dangling Pointer und beim nächsten Zugriff darauf (z.B. durch MessageAllSubscribers) knallts.

Wie verwalte ich denn dann korrekter Weise meine Referenzen? Gibt es die Möglichkeit mir den Referenzzähler der Interface-Klasse zunutzen zu machen? Ist sowas sogar so gedacht? Wie würde ich das denn machen?

Grüße

Ob du Referenzzählung nutzt oder nicht, hängt wohl von deinem Design ab. Es gibt pro und contra. Das würde aber den Rahmen dieses Thread sprengen, darüber zu philosophieren :)

Konkret zum Thema hier: Benutz zum Speichern deiner Subscriber TList<ISubscriber>. Ob die Klassen, welche ISubcriber implementieren, Refcounting haben oder nicht, ist dann erstmal irrelevant.

Jonas Shinaniganz 8. Nov 2012 16:23

AW: Interfaces Generics
 
Folgendes kommt aus der Delphi Referenz:
Zitat:

Anmerkung: __AddRef und _Release können auch so implementiert werden, dass sie keine Referenzzählung durchführen. Bei solchen Objekten erreicht der Referenzzähler niemals den Wert Null, sodass das Objekt nicht automatisch freigegeben wird. In diesen Fällen muss die Anwendung für die Freigabe des Objekts sorgen.
Also wenn ich immer meine Objekte von TFischer / TMeier aus der Liste mit RemoveSubscriber entferne und auch selbst den Speicher von allen erzeugten Objekten wieder Freigebe dann sollte ich doch keine Probleme bekommen oder?

Allerdings schaffe ich es nicht, dass meine Objekte sich automatisch (zb im destructor) aus der Liste austragen, so wie mein Konzept bisher ist.

Sir Rufo 8. Nov 2012 22:57

AW: Interfaces Generics
 
Warum sollten sich deine Objekte auch selber aus der Liste austragen?

Ein Interface hat den besonderen Vorteil, dass es sich selber aus dem Speicher entfernt, wenn keiner mehr etwas von ihm wissen möchte.

Nehmen wir an dein System verwaltet die Newspaper von 3 Publishern.
Bei allen tragen sich ein paar Leutchen als Empfänger (Subscriber) ein, davon einige auch bei 2 oder sogar 3 Publishern.

Irgendwann hat einer (Subscriber) keine Lust mehr und meldet sich von einem Publisher wieder ab.

Ist dieser jenige Subscriber bei keinem anderen Verlag mehr angemeldet, dann verschwindet er automatisch aus dem System. Das ist auch gut so, denn für die Verwaltung wird diese Person nicht mehr benötigt. Er bekommt ja von keinem mehr ein Newspaper, wozu also damit noch belasten.

Es funktioniert also genau anders herum. Das Objekt (Interface) wird nicht gelöscht und dadurch aus den Listen entfernt, sondern wenn es aus allen Listen entfernt wurde, dann verschwindet es im Daten-Nirwana.

Deine Deklaration vom Publisher ist etwas suboptimal. Wozu sind die Methoden als
Delphi-Quellcode:
virtual; abstract;
deklariert?
Diese Methoden sind für alle abgeleiteten Klassen gleich und können direkt in
Delphi-Quellcode:
TPublisher
implementiert werden.
Und wie willst du in einer abgeleiteten Klasse auf das private (upps, das war ja protected ... o graus) Feld
Delphi-Quellcode:
FSubscribers
zugreifen? Das würde nur funktionieren, wenn sich die abgeleiteten Klassen in der selben Unit befinden.
Delphi-Quellcode:
type
  TNewspaper = class
  private
    FTitle : String;
    procedure SetTitle(const Value : String);
  public
    property Title : String read FTitle write SetTitle;
  end;

  ISubscriber = interface
    procedure GetNewspaper(Newspaper : TNewspaper);
  end;

  TPublisher = class
  private
    FSubscribers : TList<ISubscriber>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure AddSubscriber(Subscriber : ISubscriber);
    procedure RemoveSubscriber(Subscriber : ISubscriber);
    procedure SendNewsToAllSubs( Newspaper : TNewspaper );
  end;

implementation

constructor TPublisher.Create;
begin
  inherited;
  FSubscribers := TList<ISubscriber>.Create;
end;

destructor TPublisher.Destroy;
begin
  FSubscribers.Free;
  inherited;
end;

procedure TPublisher.AddSubscriber(Subscriber : ISubscriber);
begin
  FSubscriber.Add( Subscriber );
end;

procedure TPublisher.RemoveSubscriber(Subscriber : ISubscriber);
begin
  FSubscriber.Remove( Subscriber );
end;

procedure TPublisher,SendNewsToAllSubs( Newspaper : TNewspaper );
var
  LSubscriber : ISubscriber;
begin
  for LSubscriber in FSubscribers do
    LSubscriber.GetNewspaper( Newspaper );
end;
Ab jetzt kann jeder Publisher Subscriber aufnehmen und entfernen, sowie Newspaper an seine Subscriber ausliefern, ohne bei der Ableitung noch weiteren Code hinzufügen zu müssen.

Elvis 9. Nov 2012 10:04

AW: Interfaces Generics
 
Mag offtopic sein, aber BÜDDE, BÜDDE, mit Zucker obendrauf: Benutzt richtige Calling canventions wenn ihr schon Interfaces nehmt.

Delphi Interfaces sind COM-kompatibel. Man könnte also eigentlich problemlos Implemtierungen in C#, C++, FPC, D, etc. erzeugen.
Aber nur wenn nicht Borlands Fastcall AKA register benutzt wird!
Also safecall oder stdcall, aber nicht gar nix...

Wollte letztlich was mit der OTA machen und hatte ein recht schmerzhaftes Facepalm als ich sah, dass die Honks von Borland die OTA-Interfaces tatsächlich in register geschrieben haben. :wall:

JamesTKirk 9. Nov 2012 10:15

AW: Interfaces Generics
 
Zitat:

Zitat von Elvis (Beitrag 1190473)
Mag offtopic sein, aber BÜDDE, BÜDDE, mit Zucker obendrauf: Benutzt richtige Calling canventions wenn ihr schon Interfaces nehmt.

Delphi Interfaces sind COM-kompatibel. Man könnte also eigentlich problemlos Implemtierungen in C#, C++, FPC, D, etc. erzeugen.
Aber nur wenn nicht Borlands Fastcall AKA register benutzt wird!
Also safecall oder stdcall, aber nicht gar nix...

Ich weiß jetzt nicht, wofür Jonas Shinaniganz die Interfaces benötigt, aber ich selbst würde spezifische Calling Conventions nur dann verwenden, wenn ich die Interfaces auch wirklich exportiere. Ansonsten nehme ich den Default und der ist nun mal unter x86
Delphi-Quellcode:
register
.

Gruß,
Sven

Elvis 9. Nov 2012 10:23

AW: Interfaces Generics
 
Zitat:

Zitat von JamesTKirk (Beitrag 1190475)
Ich weiß jetzt nicht, wofür Jonas Shinaniganz die Interfaces benötigt, aber ich selbst würde spezifische Calling Conventions nur dann verwenden, wenn ich die Interfaces auch wirklich exportiere. Ansonsten nehme ich den Default und der ist nun mal unter x86
Delphi-Quellcode:
register
.

Und in der nächsten Version willst du dann auch Plugins erlauben, und die kann man dann nur mit Delphi/BCB oder vllt FPC schreiben. :-)

Oder du willst eine Funktion aus .Net/C++/.etc benutzen, und kannst deine Instanzen nicht direkt übergeben, sondern musst sie erst verpacken, nur weil du kein "safecall" oder "stdcall" hinter die Methoden namen packen wolltest.

Klasse! :thumb:

Stevie 9. Nov 2012 10:53

AW: Interfaces Generics
 
Zitat:

Zitat von Elvis (Beitrag 1190473)
Mag offtopic sein, aber BÜDDE, BÜDDE, mit Zucker obendrauf: Benutzt richtige Calling canventions wenn ihr schon Interfaces nehmt.

Delphi Interfaces sind COM-kompatibel. Man könnte also eigentlich problemlos Implemtierungen in C#, C++, FPC, D, etc. erzeugen.
Aber nur wenn nicht Borlands Fastcall AKA register benutzt wird!
Also safecall oder stdcall, aber nicht gar nix...

Wollte letztlich was mit der OTA machen und hatte ein recht schmerzhaftes Facepalm als ich sah, dass die Honks von Borland die OTA-Interfaces tatsächlich in register geschrieben haben. :wall:

Nur, weil die Delphi Interfaces COM kompatibel sind, heißt das nicht, dass man überall eine COM kompatible Calling convention benutzen muss.
Falls man wirklich mal Compiler übergreifend arbeiten will, ist wohl stdcall hinten dran schreiben das geringste Problem oder benutzt du in Interfaces Signaturen nur COM kompatible Datentypen (TObject in C#? Viel Spaß!)


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:07 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