Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi gemischter Interface-/Objektzugriff (https://www.delphipraxis.net/192770-gemischter-interface-objektzugriff.html)

Lemmy 18. Mai 2017 08:38

Delphi-Version: 7

gemischter Interface-/Objektzugriff
 
Servus,

beim Einsatz von Interfaces muss man ja tunlichst darauf achten, Interfaces und Objektzugriff nicht zu mischen, da es sonst zur automatischen Freigabe kommen kann, da die Objektvariable nicht zum RefCount zählt.

Nehmen wir folgendes Beispiel

Delphi-Quellcode:
  IFooItem = Interface
   ['{9995E78E-45DF-4C25-B657-7396738FEA70}']
   procedure Bar;
  end;

  TFooItem = class(TinterfacedObject, IFooItem)
  public
    procedure Bar;
    procedure Foo(const AValue);
  end;

Der Konsument meines Interfaces muss lediglich auf die Methode Bar zugreifen. Allerdings benötige ich beim Erzeugen / Initialisieren / Aktualisieren in meinem Owner der Objektinstanz auch die Methode Foo.

Delphi-Quellcode:
  TFooOwner = class
  private
    Fitem: TFooItem
    function GetItem: IFooItem
  public
    property Item: IFooItem getItem;
    procedure ChangeItem(const AValue);
  end;#


....
 procedure TFooOwner.ChangeItem(const AValue)
 begin
   FItem.Foo(AValue);
 end;
Greife ich mit dem Code oben einmal auf Item zu wird aus dem FItem das Interface geholt, übergeben und wenn die aufrufende Methode beendet ist, wird das Interface genilt, der Refcounter kommt bei 0 an und FItem wird frei gegeben.

Die einzige Lösung für das Problem die ich kenne wäre, die Referenzzählung zu "deaktivieren". Die Freigabe der Instanz ist ja kein Problem, da ein Eigentümer da ist, der sich darum kümmert.

Allerdings wird im Netz (und auch hier) davon abgeraten die Referenzzählung zu umgehen - nur warum? Oder Alternativ: Welche andere Möglichkeit gibt es für eine solche Implementierung?

Uwe Raabe 18. Mai 2017 09:03

AW: gemischter Interface-/Objektzugriff
 
Grundsätzlich gilt: Solange du weißt, was du tust, ist alles erlaubt.

Für deinen Fall würde ich statt von
Delphi-Quellcode:
TInterfacedObject
von
Delphi-Quellcode:
TInterfacedPersistent
ableiten, das genau dafür gemacht ist.

himitsu 18. Mai 2017 09:08

AW: gemischter Interface-/Objektzugriff
 
Erstmal mußt du entscheiden was führend ist und der Großteil der Referenzen sollte dann in Modus sein.

1: Interface mit Referenzzählung und wenn nötig, dann wird daraus "kurzzeitig" eine Objektreferenz geholt (z.B. TInterfacedObject)
2: Objekt ohne Referenzzählung ist führend und man kann daraus kurzzeitig Interfacereferenzen holen (z.B. TInterfacedPersistent)

Ein Beispiel für 2 ist die VCL, da kannst du jedes TComponent nach einem Interface fragen und so gleiche Funktionalität in unterschiedliche Klassen (auch nachträglich) einbauen.

PS: Etwas neuere Delphis können auch
Delphi-Quellcode:
MyIntf as TMyObject
, da dort ein virtuelles Interface in TObject steckt, was "intern" per Supports statt einer Interfacereferenz die Objektreferenz raus holt, ohne das man selber ein GetMyObject (Result := Self) bauen muß.

PPS: Unter ARC hat Delphi Interfaces und Objekte vermanscht.
Beides ist "immer" referenzgezählt (außer bei
Delphi-Quellcode:
var [Weak] Variable: xxx;
) und nutzt eine gemeinsame Zählung.

Lemmy 18. Mai 2017 09:33

AW: gemischter Interface-/Objektzugriff
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1371839)
Grundsätzlich gilt: Solange du weißt, was du tust, ist alles erlaubt.

LOL
ich weiß, dass ich nichts weiß.... :-)

Danke für Eure Tipps, über TInterfacedPersistent bin ich auch schon gestolpert, war mir aber etwas unsicher bzgl. des FOwnerInterface. Schaut aber ganz gut aus im ersten Test...

Zitat:

Zitat von himitsu (Beitrag 1371842)
PS: Etwas neuere Delphis können auch
Delphi-Quellcode:
MyIntf as TMyObject
, da dort ein virtuelles Interface in TObject steckt, was "intern" per Supports statt einer Interfacereferenz die Objektreferenz raus holt, ohne das man selber ein GetMyObject (Result := Self) bauen muß.

ja, das habe ich auch gelesen. Bin aber noch ein paar Wochen im Delphi 7 Modus. Allerdings finde ich das aktuell noch ziemlich böse :-)

himitsu 18. Mai 2017 10:33

AW: gemischter Interface-/Objektzugriff
 
Eigentlich ist es ja böse, wenn man aus einem "abstrakten" Inteface ein Objekt macht, da ja die Implementation hinter dem Interface eigentlich "geheim" ist.

Aber aus Sicht des Delphi und wenn man hinter dem Interface ein Objekt hat, ist es so vollständiger.
* Objekt zu Interace geht ja schon ewig und ganz einfach (auch ohne Delphi-Referenz durchsuchenSupports)
* und nun auch andersrum

Geht seit Galileo-IDE Version 7, also ab Delphi 2010.
Blöd für dich ... Delphi 7 ist Delphi-IDE Version 7, aber das war auch die letzte Version des AppBuilder (genannt Delphi)

Delphi-Quellcode:
var
  I: IInterfaceComponentReference;
  O: TComponent;
begin
  I := TComponent.Create(Self);
  O := TComponent.Create(Self);
  I := O as IInterfaceComponentReference; // nötig, wenn I z.B. nur IInterface wäre
  I := O; // hier wird das Interface der Variable genommen
  O := I as TComponent; // und bei Objekten ist alles die selbe Referenz, egal auf welche Klasse es gecastet wurde, aber bei I>O ist der Cast nur "explizit" möglich
end;
Vor Delphi 2010 brauchte man z.B. sowas
Delphi-Quellcode:
IInterfaceComponentReference = interface
  ['{E28B1858-EC86-4559-8FCD-6B4F824151ED}']
  function GetComponent: TComponent;
end;

function TComponent.IntfGetComponent: TComponent; // function IInterfaceComponentReference.GetComponent = IntfGetComponent;
begin
  Result := Self;
end;

freimatz 18. Mai 2017 13:40

AW: gemischter Interface-/Objektzugriff
 
Zitat:

Zitat von Lemmy (Beitrag 1371828)
Der Konsument meines Interfaces muss lediglich auf die Methode Bar zugreifen. Allerdings benötige ich beim Erzeugen / Initialisieren / Aktualisieren in meinem Owner der Objektinstanz auch die Methode Foo.

Ei warum denn? Ich vermute das Problem eher hier.
Dass Du die Methode Foo brauchst - ok. Aber dann auf die Objektinstanz zuzugreifen halte ich für falsch. Du könntest
a) Foo auch auf das Interface geben.
b) ein zweiters Interface machen
Ich tendiere dazu Implementierungen von interfaces komplett zu verstecken und nur noch eine Fabrikmethode nach außen zu geben.

Lemmy 18. Mai 2017 13:49

AW: gemischter Interface-/Objektzugriff
 
Zitat:

Zitat von freimatz (Beitrag 1371909)
a) Foo auch auf das Interface geben.
b) ein zweiters Interface machen

a) will ich nicht, weil der Konsument da nix zu schreiben hat. und b) Zeig doch bitte Code

Delphi-Quellcode:
  IFooItem = Interface
   ['{9995E78E-45DF-4C25-B657-7396738FEA70}']
   procedure Bar;
  end;

  IFooFooItem =Interface
   ['{9995E78E-45DF-4C25-B657-7396738FEA70}']
    procedure Foo(const AValue);
  end;

  TFooItem = class(TinterfacedObject, IFooItem, IFooFooItem)
  public
    procedure Bar;
    procedure Foo(const AValue);
  end;
und jetzt der Owner:

Delphi-Quellcode:
 TFooOwner = class
  private
    Fitem: XXXXX
    function GetItem: IFooItem
  public
    property Item: IFooItem getItem;
    property FooItem: IFooFooItem GetFooItem;
  end;
Wie wird jetzt die Instanz von FItem gespeichert? Als einzigste Lösung würde mir wieder die ObjektInstanz einfallen und in den Get* Methoden dann ein GetInterface(IFooXItem, Result)aber dann habe ich wieder einen Zugriff auf die Objektinstanz!?!?

stahli 18. Mai 2017 14:12

AW: gemischter Interface-/Objektzugriff
 
Es gibt kurz gesagt, eigentlich drei wesentliche Gründe für den Einsatz von Interfaces unter Delphi:

- gleiche Funktionalität in unterschiedlichen Klassen (ohne gemeinsame Basisklasse) als Alternative zur nicht vorhandenen Mehrfachvererbung
- Benutzung von Referenzzählung und automatischer Freigabe (wenn man das will)
- evtl. Einbindung von DLLs

Man erreicht eine abstrakte Beschreibung von Datenfeldern und Funktionen, ohne die genaue Implementation in den Klassen kennen zu müssen.

In Deinem Bespiel erkenne ich nicht, warum Du in einem Fall plötzlich mit Interfaces arbeiten willst/musst.

Werden ganz unterschiedliche Klassen an Deine Funktion übergeben?

Den geringsten Umstellungsaufwand hast Du sicher mit dem Vorschlag von Uwe.
Dann kannst Du einfach normal mit Deinen Objekten arbeiten und dieses einfach einmalig in Form eines Interfaces an die Funktion übergeben.

Eine andere Möglichkeit wäre die Umstellung insgesamt auf Interfaces, das würde aber einiges an Mehraufwand und grundsätzlichen Änderungen mit sich bringen.

Interfaces sind nützlich, aber man sollte abwägen, ob sie im vorliegenden Fall Vorteile bringen...

Fritzew 18. Mai 2017 14:27

AW: gemischter Interface-/Objektzugriff
 
Zitat:

- gleiche Funktionalität in unterschiedlichen Klassen (ohne gemeinsame Basisklasse) als Alternative zur nicht vorhandenen Mehrfachvererbung
- Benutzung von Referenzzählung und automatischer Freigabe (wenn man das will)
- evtl. Einbindung von DLLs
Für mich der wichtigste Punkt:
- Decoupling des Codes, also keine Abhängigkeiten im Source ala Class1 kennt Class2 kennt Class3 die wieder Class1 kennt, etc..
Dadurch wird der Code auch besser Testbar. Wenn man dann noch hingeht und die Interfaces in die Klassen hineingibt, bekommt man schlanken wartbaren Code.

himitsu 18. Mai 2017 14:31

AW: gemischter Interface-/Objektzugriff
 
Das kannst du aber auch mit abstakten Klassen erreichen.

Das extern Bekannte wird abstrakt definiert und deine Klasse1-3 leiten dann halt jeweils von sowas ab.
siehe TStrings oder TStream

Fritzew 18. Mai 2017 14:37

AW: gemischter Interface-/Objektzugriff
 
Zitat:

Zitat von himitsu (Beitrag 1371933)
Das kannst du aber auch mit abstakten Klassen erreichen.

Das extern Bekannte wird abstrakt definiert und deine Klasse1-3 leiten dann halt jeweils von sowas ab.
siehe TStrings oder TStream

Klar, aber in meinen Augen ist eine Abstrakte Klasse eigentlich auch nichts anderes als ein Interface. Halt ohne Referenzzählung. Und ich muss von der Basisklasse ableiten. Aber das ist eine Wahl die jeder für sich und seinen Code entscheiden muss.

Lemmy 18. Mai 2017 14:49

AW: gemischter Interface-/Objektzugriff
 
Zitat:

Zitat von stahli (Beitrag 1371924)

In Deinem Bespiel erkenne ich nicht, warum Du in einem Fall plötzlich mit Interfaces arbeiten willst/musst.

weil ich einen Part aus dem aktuellen Delphi 7 Projekt in einem neueren Delphi implementieren muss, weil es mit Delphi 7 einfach Probleme macht / nicht funktioniert. Und da die Gesamtumstellung noch auf sich warten lässt wandert der Part in die DLL.

Und die DLL (als Konsument) soll dabei nur die notwendigsten Informationen/Möglichkeiten bekommen, in meiner Anwendung aber brauch ich gerade zum Zeitpunkt der Initialisierung die eine oder andere Funktion mehr.

Beispiel: Eine Liste von Objekten (bzw. Interfaces) die in der DLL abgearbeitet werden müssen. Die DLL braucht weder neue Elemente der Liste hinzufügen noch löschen, daher definiert das dafür zuständige Listen-Interface auch nur eine get und eine count Methode.

mjustin 18. Mai 2017 17:23

AW: gemischter Interface-/Objektzugriff
 
Spannende Frage. Ich würde es intuitiv so machen:

Delphi-Quellcode:
  private
    FItem: IFooItem;
    FFooItem: IFooFooItem;
und dann im Kontruktor:

Delphi-Quellcode:
constructor TFooOwner.Create(const AItem: TFooItem);
begin
  inherited Create;

  FItem := AItem;

  FFooItem := AItem;
end;
Das mit der Referenzzählung ist immer spaßig, man kann dann aber auch über Logging der aktuellen Referenzanzahl einiges schneller aufdecken als im Debugger.

freimatz 18. Mai 2017 17:24

AW: gemischter Interface-/Objektzugriff
 
Bischen viel Quote, Sorry
Zitat:

Zitat von Lemmy (Beitrag 1371914)
Zitat:

Zitat von freimatz (Beitrag 1371909)
a) Foo auch auf das Interface geben.
b) ein zweiters Interface machen

a) will ich nicht, weil der Konsument da nix zu schreiben hat. und b) Zeig doch bitte Code

ok ich ergänze gleich mal deinen

Delphi-Quellcode:
  IFooItem = Interface
   ['{9995E78E-45DF-4C25-B657-7396738FEA70}']
   procedure Bar;
  end;

  IFooFooItem =Interface
   ['{9995E78E-45DF-4C25-B657-7396738FEA70}']
    procedure Foo(const AValue);
  end;

  TFooItem = class(TinterfacedObject, IFooItem, IFooFooItem)
  public
    procedure Bar;
    procedure Foo(const AValue);
  end;
 
  function CreateItem():IFooItem;
 
...
 
  function CreateItem():IFooItem;
  begin
    Item := TFooItem.Create() as IFooItem;
  end;
und jetzt der Owner:

Delphi-Quellcode:
 TFooOwner = class
  private
    Fitem: IFooItem
    function GetItem: IFooItem
  public
    property Item: IFooItem getItem;
    property FooItem: IFooFooItem GetFooItem;
  end;
...
procedure TFooOwner.DoAbc();
var FooFooItem: IFooFooItem;
begin
  if not Supports(Item, IFooFooItem) then raise EPanic...;
  FooFooItem := Item as IFooFooItem;
  FooFooItem.Foo(abc)
end;
Zitat:

Zitat von Lemmy (Beitrag 1371914)
Wie wird jetzt die Instanz von FItem gespeichert? Als einzigste Lösung würde mir wieder die ObjektInstanz einfallen und in den Get* Methoden dann ein GetInterface(IFooXItem, Result)aber dann habe ich wieder einen Zugriff auf die Objektinstanz!?!?

Gespeichert als interface. Immer und überall. Vermischen ist böse.
Die beiden interfaces würde ich dann auch nicht in eine unit packen.

Stevie 18. Mai 2017 19:06

AW: gemischter Interface-/Objektzugriff
 
Ich sehe da einen gewissen "friend" Status bei deinen beiden Klassen. In solchen Fällen lasse ich da auch schonmal fünfe gerade sein und nutze intern die Klasse, auf welche extern nur über das Interface zugegriffen wird als Objekt.

Handelt es sich dabei um TInterfacedObject Nachfahren, nutz ich _AddRef und _Release an den Stellen wo ich mein internes Objekt feld setze bzw wenn der Owner freigegeben wird.

Das hat denselben Effekt wie als Interface zu speichern aber ich kann noch auf die Methoden, die nicht im Interface vorhanden sind zugreifen (plus wenn relevant bessere Performance und sogar inlining möglich, aber das sei nur am Rande erwähnt)

Ob du das machst oder von einer Klasse erbst, die keine Referenzzählung und entsprechende Speicherverwaltung implementiert, hängt letztlich davon ab, was per Design länger lebt, wenn das irgendwelche Referenzen auf das Interface sein können, dann nutz ich die erste Möglichkeit um keine Interface referenzen auf schon freigegebene Instanzen zu befürchten, ist es anders herum, kann man die zweite Möglichkeit erwägen.

Blup 24. Mai 2017 09:47

AW: gemischter Interface-/Objektzugriff
 
Wenn beim Erzeugen des Objektes zusätzliche Methoden oder Properties angesprochen werden, braucht man dafür eigentlich kein zweites Interface.
Man muss nur klar abgrenzen, bis wo ohne Referenzzählung mit dem Objekt gearbeitet wird. Danach sollte das Objekt nicht mehr direkt referenziert werden.
Delphi-Quellcode:
type
  IFooItem = Interface
   ['{9995E78E-45DF-4C25-B657-7396738FEA70}']
    procedure Foo(const AValue);
  end;

  TFooItem = class(TinterfacedObject, IFooItem)
  public
    procedure Bar;
    procedure Foo(const AValue);
  end;

  TFooOwner = class
  private
    FItem: IFooItem
    function CreateItem: TFooItem;
    function GetItem: IFooItem;
  public
    property Item: IFooItem GetItem;
  end;

implementation

function TFooOwner.CreateItem: TFooItem;
begin
  Result := TFooItem.Create;
  Result.Bar;
end;

function TFooOwner.GetItem: IFooItem;
begin
  if not Assigned(FItem) then
    FItem := CreateItem; // Referenzzählung beginnt
 
  Result := FItem;
end;


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