Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Klonen eines Interfaces (https://www.delphipraxis.net/212125-klonen-eines-interfaces.html)

Jasocul 22. Dez 2022 07:48

Klonen eines Interfaces
 
Ich habe gerade das Gefühl ein Brett vor dem Kopf zu haben.
Beim Refactoring einer vorhandenen Software baue ich gerade etwas zu einem Interface um. Vorher war das ein einfacher Record und eine Unit mit ein paar Prozeduren und Funktionen.
Ich brauche jetzt eine Methode, um die Inhalte einer Instanz auf eine andere zu kopieren. Mein Problem dabei ist, dass ich mich irgendwie im Kreis drehe. Die Instanzen werden auf Basis des Interfaces erzeugt. Die Inhalte sind aber zum Teil erst im InterfacedObject enthalten, bzw. über Properties erreichbar, deren Setter aber dazu führt, dass interne Changed-Kennzeichen gesetzt werden, was ich aber beim Kopieren nicht haben will.

Die Sourcen in abgespeckter Version:
Das Interface:
Delphi-Quellcode:
   IMyInt = interface
      // Setter
      procedure SetA(AValue: Boolean);
      // Getter
      function GetA: Boolean;
      function GetB: String;

      function Clone: IMyInt;
      function Save: Boolean;
      // Properties
      property A: Boolean read GetA write SetA;
      property B: String read GetB;
   end;
Die Klasse:
Delphi-Quellcode:
   TMyClass = class(TInterfacedObject, IMyInt)
   strict private
                fA: Boolean;
                fB: String;
      fChanged: Boolean;
   private
      procedure SetA(AValue: Boolean);
      function GetA: Boolean;
      function GetB: String;
   public
      constructor Create;
      destructor Destroy(); override;
      function Clone: IMyInt;
      function Save: Boolean;
      property A: Boolean read GetA write SetA;
      property B: String read GetB;
   end;
....
procedure TMyClass.SetA(AValue: Boolean);
begin
   if fA <> AValue then begin
      fChanged := True;
      fA := AValue;
   end;
end;
In der Anwendung selbst, gibt es bereits zwei Instanzen. Es geht also nur darum, die Inhalte von einer Instanz in die Andere zu kopieren, ohne automatische fChanged zu setzen und dabei auch alles zu kopieren. Für Property B gibt es zum Beispiel keinen Setter. Das ganze würde ich gerne über eine Methode innerhalb der Klasse lösen. Da die Instanzen vom Typ IMyInt sind, kann ich nicht auf die Member-Variablen zugreifen. Ich habe gestern noch einiges probiert, hatte aber irgendwann das Gefühl mich im Kreis zu drehen und mir selbst den Blick für die Lösung zu verstellen.

Kann ich jemand mal in die richtige Richtung schubsen?

perpeto1234 22. Dez 2022 08:02

AW: Klonen eines Interfaces
 
Meinst du sowas?

Delphi-Quellcode:
function TMyClass.Clone: IMyInt;
begin
  Result := TMyClass.Create;
  TMyClass(Result).fA := Self.fA;
end;

exon 22. Dez 2022 08:05

AW: Klonen eines Interfaces
 
hm, zusätzliche Variable InCloning, also so:


Code:
   TMyClass = class(TInterfacedObject, IMyInt)
   strict private
                fA: Boolean;
                fB: String;
      fChanged: Boolean;
      fInCloning: Boolean;
   private
      procedure SetA(AValue: Boolean);
      function GetA: Boolean;
      function GetB: String;
   public
      constructor Create;
      destructor Destroy(); override;
      function Clone: IMyInt;
      function Save: Boolean;
      property A: Boolean read GetA write SetA;
      property B: String read GetB;
   end;
....
procedure TMyClass.SetA(AValue: Boolean);
begin
   if fA <> AValue then
   begin
      if not fInCloning then
        fChanged := True;
      fA := AValue;
   end;
end;

function TMyClass.Clone: IMyInt;
begin
   fInCloning := True;
   try
      <bisheriger Clone-Code>
   finally
     fInCloning := False;
   end;
end;

mjustin 22. Dez 2022 08:23

AW: Klonen eines Interfaces
 
Das hier sollte funktionieren:

Delphi-Quellcode:
function TMyClass.Clone: IMyInt;
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  MyClass.fA := Self.fA;
  MyClass.fB := Self.fB;
  Result := MyClass
end;
(sehe dass die Lösung schon vorgeschlagen wurde - einziger Unterschied ist der "hard cast")

(siehe https://stackoverflow.com/a/8383009/80901)

perpeto1234 22. Dez 2022 08:24

AW: Klonen eines Interfaces
 
Zitat:

Zitat von mjustin (Beitrag 1516541)
Das hier sollte funktionieren:

Delphi-Quellcode:
function TMyClass.Clone: IMyInt;
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  MyClass.fA := Self.fA;
  MyClass.fB := Self.fB;
  Result := MyClass
end;
(siehe https://stackoverflow.com/a/8383009/80901)

müsste aber "var MyClass: IMyInt" sein, sonst gibts Probleme mit der Referenzzählung, oder nicht?

mjustin 22. Dez 2022 08:27

AW: Klonen eines Interfaces
 
Zitat:

Zitat von perpeto1234 (Beitrag 1516542)
müsste aber "var MyClass: IMyInt" sein, sonst gibts Probleme mit der Referenzzählung, oder nicht?

Die Referenzzählung funktioniert auch bei diesem Konstrukt wie erwartet (automatische Freigabe, keine memory leaks).

Ein bekannter und berüchtigter Referenzzählungs-Bug entsteht, wenn man an einen Parameter, der einen Interfacetyp hat, eine frisch erzeugte Instanz übergibt:

Delphi-Quellcode:
procedure Machwas(MyInt: IMyInt);
...
...
...
MachWas(TMyClass.Create); // bad things will happen

Jasocul 22. Dez 2022 08:30

AW: Klonen eines Interfaces
 
Vielen Dank exon und perpeto1234.
Ich kann jetzt nicht sagen, welcher Vorschlag bei mir den Knoten hat platzen lassen, aber ich habe einen neuen Ansatz gefunden.
Es wird jetzt nicht über eine Clone-Funktion gemacht, die von der Quelle kommt, sondern eine Prozedure "CloneFrom" die als Parameter die Quelle bekommt. Dann kann ich auf alle vorhandenen Member-Variablen zugreifen und alles ohne Setter setzen.

@mjustin:
Deinen Ansatz werde ich mir merken. Der passt hier aber nicht ganz, da Quelle und Ziel als Instanzen bereits existieren.

mjustin 22. Dez 2022 08:36

AW: Klonen eines Interfaces
 
Zitat:

Zitat von Jasocul (Beitrag 1516544)
Deinen Ansatz werde ich mir merken. Der passt hier aber nicht ganz, da Quelle und Ziel als Instanzen bereits existieren.

Stimmt, nur dies sollte gehen, d.h. wenn nicht ein Interface sondern eine Instanz übergeben wird:

Delphi-Quellcode:
procedure TMyClass.CopyFrom(AOther: TMyClass);
begin
  Self.fA := AOther.fA;
  Self.fB := AOther.fB;
end;

Jasocul 22. Dez 2022 08:38

AW: Klonen eines Interfaces
 
Im Prinzip ist das genau der Ansatz, den ich jetzt genommen habe. Das geht auch mit dem Interface als Parameter, wenn die Properties auch im Interface deklariert sind.

himitsu 22. Dez 2022 09:36

AW: Klonen eines Interfaces
 
PS: Wenn es dir nur darum geht, dass es wie ein Interface/Objekt aussieht, also die Methoden und die Daten zusammen und die Funktionen in der "Klasse" drin,
dann kann man die einzelnen Funktionen auch als Methoden in den Record verschieben, statt einem Parameter auf Self zugreifen und fertig.

Bezüglich des Kopierens kannst dann diesen Record weiterhin genauso behandeln, wie bisher.




Ansonsten nimmt man im Allgemeinen eine Funktion, welche sich Delphi-Referenz durchsuchenTPersistent.Assign bzw. Delphi-Referenz durchsuchenTPersistent.AssignTo nennt.
Da drin mußt du dann die Property/Felder des einen Objekts in das andere Kopieren.

Beim normalen Assign mit allgemeinem Typen wird intern nochmals geprüft, ob der Typ passt,
aber hier kannst du einfach dem Parameter den richtigen Typen geben und schon kann nur der richtige Typ reingegeben werden.

Jasocul 22. Dez 2022 10:36

AW: Klonen eines Interfaces
 
Zitat:

Zitat von himitsu (Beitrag 1516550)
PS: Wenn es dir nur darum geht, dass es wie ein Interface/Objekt aussieht, also die Methoden und die Daten zusammen und die Funktionen in der "Klasse" drin,
dann kann man die einzelnen Funktionen auch als Methoden in den Record verschieben, statt einem Parameter auf Self zugreifen und fertig.

Damit wird später noch deutlich mehr gemacht. Ein Record wird dann sicher nicht mehr ausreichend sein. Ansonsten gebe ich dir Recht.
Zitat:

Zitat von himitsu (Beitrag 1516550)
Ansonsten nimmt man im Allgemeinen eine Funktion, welche sich Delphi-Referenz durchsuchenTPersistent.Assign bzw. Delphi-Referenz durchsuchenTPersistent.AssignTo nennt.
Da drin mußt du dann die Property/Felder des einen Objekts in das andere Kopieren.

Beim normalen Assign mit allgemeinem Typen wird intern nochmals geprüft, ob der Typ passt,
aber hier kannst du einfach dem Parameter den richtigen Typen geben und schon kann nur der richtige Typ reingegeben werden.

Mit Assign hatte ich es gestern auch versucht, bin dann aber irgendwie gescheitert (Die richtige Basisklasse habe ich dafür natürich genommen). Vermutlich war es gestern einfach schon zu spät und ich habe den Knoten nicht mehr aus den Gehirn bekommen. Ich lege mir aber gleich nochmal ein ToDo an, um das nochmal zu prüfen.

EDIT:
Ich bin nicht gescheitert, aber da die Instanzen vom Typ IMyInt sind, hätte ich hart auf TMyClass casten müssen, damit das assign funktioniert. Da ich kein Freund von solchen Casts bin, habe ich nach einer anderen Lösung gesucht. Wenn es einen Weg ohne den Cast gibt, bräuchte ich einen Tipp.

himitsu 22. Dez 2022 12:04

AW: Klonen eines Interfaces
 
Ja, die vordefinierten Assign nehmen nur persistente Klassen an.
seit paar Jahren kann man in Delphi Interfaces (in denen ein Delphi-Objekt steckt) ganz einfach mit AS in ein Objekt casten.

Wenn alles Kopierbare aber als Property/Funktionen lesbar und schreibbar ist, kannst du auch einfach diese Property/Funktionen des Interfaces zum Kopieren der Eigenschaften benutzen und mußt nicht casten.



Wie gesagt, DU mußt die Eigenschafften einzeln übertragen, auch wenn man es bei persistenten Klassen auch anders lösen könnte.
Diese persistenten Klassen haben ja Funktionen, um ihren Inhalt/Zustand zu serialisieren/speichern, also quasi die eine Instanz in einen Stream speichern und die andere Instanz aus dem Stream zu laden.

Aber da du ja nicht von TPersistent oder TComponent geerbt hast, mußt du eh diese Methode selber komplett neu einbauen und kannst dort auch so Einiges ändern, wie eben z.B. den Parameter als Interface.

Im Prinzip ist es egal, wie du deine Kopieren-Methode nennst, aber es macht sich besser, wenn man Methoden mit einheitlichem Verhalten auch einheitlich benennt.
Darum Assign, aber auch Clone wird da oft als Name genommen. Wobei Assign die Daten in die eigene Instanz übernimmt, AssignTo an die andere Instanz übergibt und Clone erstellt selber die neue Instanz, überträgt die Eigenschaften und gibt es als Result zurück.

Jasocul 22. Dez 2022 12:12

AW: Klonen eines Interfaces
 
Vielen Dank für deine zusätzlichen Infos.
Eine Umstellung auf TPersistent wäre in der aktuellen Phase sicher kein Problem. Aber im Moment scheint es mir so, als hätte ich mit meiner Variante den passenden Weg gefunden. Trotzdem werde ich weiterhin ein Auge darauf haben, ob nicht doch noch eine Anpassung notwendig wird.
Zitat:

Zitat von himitsu (Beitrag 1516557)
Darum Assign, aber auch Clone wird da oft als Name genommen. Wobei Assign die Daten in die eigene Instanz übernimmt, AssignTo an die andere Instanz übergibt und Clone erstellt selber die neue Instanz, überträgt die Eigenschaften und gibt es als Result zurück.

Sehr interessant. Mich hatte das heute Morgen selbst gestört und daher heißt die Methode seit ein paar Stunden schon CopyFrom. :wink:

himitsu 22. Dez 2022 12:27

AW: Klonen eines Interfaces
 
Zitat:

Zitat von Jasocul (Beitrag 1516558)
Vielen Dank für deine zusätzlichen Infos.
Eine Umstellung auf TPersistent wäre in der aktuellen Phase sicher kein Problem. Aber im Moment scheint es mir so, als hätte ich mit meiner Variante den passenden Weg gefunden. Trotzdem werde ich weiterhin ein Auge darauf haben, ob nicht doch noch eine Anpassung notwendig wird.
Zitat:

Zitat von himitsu (Beitrag 1516557)
Darum Assign, aber auch Clone wird da oft als Name genommen. Wobei Assign die Daten in die eigene Instanz übernimmt, AssignTo an die andere Instanz übergibt und Clone erstellt selber die neue Instanz, überträgt die Eigenschaften und gibt es als Result zurück.

Sehr interessant. Mich hatte das heute Morgen selbst gestört und daher heißt die Methode seit ein paar Stunden schon CopyFrom. :wink:


Hier mal ein Beispiel, was zeigt, dass Assign/Assign TO nichts automatisch macht, sondern du es machen mußt.
Delphi-Quellcode:
procedure TEditMargins.Assign(Source: TPersistent);
begin
  if Source is TEditMargins then
  begin
    FLeft := TEditMargins(Source).Left;
    FRight := TEditMargins(Source).Right;
    FAuto := TEditMargins(Source).Auto;
  end
  else
    inherited;
end;
Auch wenn das Beispiel nicht Ideal ist, denn das ELSE würde ich weglassen und Interhited immer ausgeführt werden sollte.
Denn jede Klasse kopiert die eigenen Eigenschaften, welche bei sich eingebaut wurden, und die Vorfahren kopieren ihr Zeugs usw.
Wobei ich das Inherited als Erstes aufrühre, also erst das Zeugs der Vorfahren und dann das Eigene, genauso wie man es auch im Create macht. (eigenes Erstellen und Laden zuletzt und beim Entladen und Freigeben zuerst)

Jasocul 22. Dez 2022 13:21

AW: Klonen eines Interfaces
 
Inherited würde ich beim Assign auch als erstes machen. Anderenfalls bestünde die Gefahr, dass manuell gesetzte Werte durch ein folgendes inherited wieder überschrieben würden.

Allerdings bin ich jetzt ein wenig verwirrt. Du schreibst, dass Assign nichts automatisch macht und man die Werte selbst übernehmen muss. Andererseits kopiert jede Klasse die eigenen Eigenschaften. Wenn ich also in deinem Beispiel ein TEditMargins als Parameter übergebe, müssten doch alle eigenen Eigenschaften übernommen werden, also auch die, die im Beispiel manuell zugewiesen werden, oder?

Uwe Raabe 22. Dez 2022 13:29

AW: Klonen eines Interfaces
 
Zitat:

Zitat von himitsu (Beitrag 1516559)
denn das ELSE würde ich weglassen und Interhited immer ausgeführt werden sollte.

Das darf es aber nicht! Zumindest nicht immer. Die Implementierung von TPersistent.Assign leitet nämlich zu Source.AssignTo und das ist in der Regel nicht gewollt, sondern eher schädlich, wenn die aktuelle Klasse die Zuweisung im Assign selbst vornimmt.

Im Assign das inherited aufzurufen darf also nur dann passieren, wenn nicht direkt von TPersistent abgeleitet wurde und eine dazwischen liegende Klasse den Aufruf von TPersistent.Assign verhindert.


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