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/)
-   -   Abgeleitete Klassen auf einzelne Units verteilen (https://www.delphipraxis.net/211083-abgeleitete-klassen-auf-einzelne-units-verteilen.html)

BastiFantasti 26. Jul 2022 07:56

Abgeleitete Klassen auf einzelne Units verteilen
 
Hallo zusammen,

ich bin gerade am überlegen, wie ich mehrere von einer Elternklasse abgeleitete Unterklassen in einzelne Units auslagern kann -
und vor allem welche Units dann nachher für die Verwendung implementiert werden müssen.

Meine angedachte Struktur sieht so aus:

Code:
class TParentDevice

class TDeviceType1(TParentDevice)

class TDeviceType2(TParentDevice)

class TDeviceType3(TParentDevice)
Aufgrund des Umfangs der jeweiligen Implementierungen möchte ich die abgeleiteten Klassen nun in separate Units stecken.

z.B.

Code:
uParentDevice -> TParentDevice
uDevType1 -> TDeviceType1
uDevType2 -> TDeviceType2
uDevType3 -> TDeviceType3
In den jeweiligen uDevType* Units muss die uParentDevice unit in die "uses" Liste aufgenommen werden
um die TParentDevice Klasse ableiten zu können.

Wie aber werden nun die abgeleiteten Klassen richtig in einer Anwendung implementiert bzw. importiert?
Ich könnte natürlich in einer Case Struktur oder über Defines die jeweiligen Konstruktoren in jeder Unit in der die Klassen benötigt werden implementieren.
Dies hätte aber zur Folge, dass ich alle neu hinzukommenden Implementierungen immer in den uses aller Units nachführen müsste.
Zudem wird der Code dadurch nicht leserlicher wenn immer noch mehr defines oder cases hinzu kommen.

Ich denke, ich werde eine weitere Unit mit einer weiteren Klasse für die Implementierung in die jeweilige Anwendung anlegen - so zu sagen als Wrapper.
So käme zu den 4 o.g. Units noch eine weitere dazu, die die direkte Schnittstelle zur Anwendung darstellt und dort in die uses aufgenommen wird.

z.B. ein TDeviceHandler der im Constructor den jeweiligen DeviceType als TypeDef übergeben bekommt und intern die jeweilige Implementierung instanziert.
Der Vorteil wäre, dass neu hinzukommende abgeleitete Klassen nur in dem TDeviceHandler bekannt gegeben und implementiert werden müssen.


evtl. so:
Code:
var
   dh : TDeviceHandler;

begin
  dh := TDeviceHandler.create(dtDeviceType1);
  dh.free;
end;
und intern dann in dem TDeviceHandler.constructor

Code:

var
  MyDevice : TParentDevice;
begin
  case dtDeviceType of:
    dtDeviceType1:
     begin
       myDevice := TDeviceType1.create;
     end;
    dtDeviceType2:
     begin
       myDevice := TDeviceType2.create;
     end;
end;
Ist der "Wrapper" Ansatz der richtige oder gibt es noch eine bessere Möglichkeit dieses Szenario zu implementieren?

Danke schon mal für eure Ideen und Anregungen :)

Viele Grüße
Bastian

Lemmy 26. Jul 2022 08:18

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Servus,

ja ist er. Schau mal nach Factory-Pattern / Factory Method. Es kann auch sein, dass Du (je nach Art und Umfang der Implementierung der Elternklasse / Kindklassen) das ganze mit Interfaces noch "aufhübschen" kannst.

Gausi 26. Jul 2022 08:27

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Wenn ich das richtig sehe, wäre die übliche Bezeichnung für deine TDeviceHandler-Klasse etwas wie TDeviceFactory.

Du hast da im wesentlichen das Konzept "Factory Pattern" gebaut.

Kann man auch noch erweitern, so dass du beim erstellen neuer abgeleiteten Klassen den Enum-Typen und den Constructor nicht erweitern musst.

Stattdessen können sich die Subklassen im initilzation-Abschnitt ihrer Unit auch bei der Factory-Klasse "registieren". Statt einem enum übergibst du dann einen String, über den die Klasse identifiziert werden kann. Die Factory kann dann nachschauen, welche Klasse zu diesem ID-String gehört, und erstellt ein dazu passendes Objekt.

Ansatz z.B. hier: https://www.delphipraxis.net/191593-...ndesign-2.html

Uwe Raabe 26. Jul 2022 09:44

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Man kann das sehr elegant auch ohne den DeviceHandler machen:

Delphi-Quellcode:
unit uParentDevice;

interface

type
  TDeviceType = (dtDeviceType1, dtDeviceType2, dtDeviceType3);

  TParentDeviceClass = class of TParentDevice;
  TParentDevice = class
  private
    class function GetDeviceClass(dtDeviceType: TDeviceType): TParentDeviceClass; static;
  public
    constructor Create; virtual;
    class property DeviceClass[dtDeviceType: TDeviceType]: TParentDeviceClass read GetDeviceClass; default;
  end;

implementation

type
  TDeviceType1 = class(TParentDevice);
  TDeviceType2 = class(TParentDevice);
  TDeviceType3 = class(TParentDevice);

constructor TParentDevice.Create;
begin
  inherited;
end;

class function TParentDevice.GetDeviceClass(dtDeviceType: TDeviceType): TParentDeviceClass;
begin
  case dtDeviceType of
    dtDeviceType1: Result := TDeviceType1;
    dtDeviceType2: Result := TDeviceType2;
    dtDeviceType3: Result := TDeviceType3;
  end;
end;
Zum Erzeugen einer passenden Instanz schreibt man dann einfach:
Delphi-Quellcode:
var
  myDevice: TParentDevice;
begin
  myDevice := TParentDevice[dtDeviceType1].Create;
end;
Werden die abgeleiteten Klassen in separate Units verteilt, entstehen natürlich zirkuläre Unit-Referenzen. Um die zu vermeiden, gibt es andere Mechanismen, über die wir dann bei Bedarf ja noch sprechen können.

BastiFantasti 26. Jul 2022 14:29

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Hallo und danke für die Antworten.

@Uwe Raabe
der Ansatz sieht in der Tat sehr schick aus. Durch diesen Ansatz will ich aber gerade das Aufsplitten der Klassen auf unterschiedliche Units erreichen.

Aktuell habe ich eine große pas Datei mit der Elternklasse sowie mehreren abgeleiteten Klassen.
Das macht das Debuggen oder das externe Vergleichen der unterschiedlichen Implementierungen mittels diff oder beyond compare sehr mühsam.

@Lemmy und @Gausi,

ich werde mir das "Factory Pattern" mal genauer anschauen und ob es da Referenzimplementierungen gibt.
Die Übergabe des jeweiligen Typs als String klingt auch sehr gut. Macht das Handling wieder etwas einfacher.

Vielen Dank schon mal. Ich werde berichten.

Viele Grüße
Bastian

Uwe Raabe 26. Jul 2022 15:26

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Zitat:

Zitat von BastiFantasti (Beitrag 1509310)
Durch diesen Ansatz will ich aber gerade das Aufsplitten der Klassen auf unterschiedliche Units erreichen.

Aktuell habe ich eine große pas Datei mit der Elternklasse sowie mehreren abgeleiteten Klassen.
Das macht das Debuggen oder das externe Vergleichen der unterschiedlichen Implementierungen mittels diff oder beyond compare sehr mühsam.

Ich hatte ja geschrieben, dass beim Verteilen der abgeleiteten Klassen in andere Units zirkuläre Referenzen entstehen. Das ist ja nicht per se verboten. Du kannst das ja erstmal machen und die jeweiligen Units in die implementation uses Anweisung von uParentDevice schreiben:
Delphi-Quellcode:
implementation

uses
  uDevType1, uDevType2, uDevType2;

constructor TParentDevice.Create;
begin
  inherited;
end;
Da die einzelnen Ableitungen an eine Enumeration gebunden sind, muss bei einer Erweiterung eh die uParentDevice angefasst werden. Die zirkulären Referenzen sind ja auch nur jeweils bidirektionaler Natur und das Ganze somit tolerierbar. Will man das Vermeiden muss man das Prinzip halt leicht abwandeln indem man z.B. deinen TDeviceHandler-Ansatz als Basis nimmt und das DeviceClass property dahin verlagert:
Delphi-Quellcode:
type
  TDeviceHandler = class
  private
    class function GetDeviceClass(dtDeviceType: TDeviceType): TParentDeviceClass; static;
  public
    class property DeviceClass[dtDeviceType: TDeviceType]: TParentDeviceClass read GetDeviceClass; default;
  end;

implementation

uses
  uDevType1, uDevType2, uDevType2;

class function TDeviceHandler.GetDeviceClass(dtDeviceType: TDeviceType): TParentDeviceClass;
begin
  case dtDeviceType of
    dtDeviceType1: Result := TDeviceType1;
    dtDeviceType2: Result := TDeviceType2;
    dtDeviceType3: Result := TDeviceType3;
  end;
end;
Der Aufruf ändert sich dann entsprechend:

Delphi-Quellcode:
var
  myDevice1: TParentDevice;
  myDevice2: TParentDevice;
begin
  myDevice1 := TDeviceHandler[dtDeviceType1].Create;
  ...
  myDevice2 := TDeviceHandler[dtDeviceType2].Create;

TurboMagic 26. Jul 2022 18:33

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
In der DEC (Delphi Encryption Compendium) gibt es für die Hashes und Verschlüsselungsalgorithmen auch so einen Klasse Registrierungsmechanismus. Über eine ID kann man da auch numerisch die passende Klasse finden.

BastiFantasti 27. Jul 2022 09:25

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
@Uwe Raabe

Vielen Dank für deine detaillierte Antwort.
Ich werde versuchen deine Implementierung nachzuvollziehen, in einem Beispiel umzusetzen und zu debuggen um zu sehen wie das Handling am Ende abläuft.


Viele Grüße
Bastian

pmuetze 28. Jul 2022 08:44

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Hi,
dass der TParentDevice die einzelnen abgeleiteten TDeviceClasses kennen (und deren Units per zirkulärer Unit-Referenz einbinden) muss ist meiner Meinung nach ein Anti-Pattern und schlechtes Design.

In so einem Fall lieber folgendes Pattern verwenden:
Spendiere der Unit mit dem TParentDevice noch eine statische TDeviceController Klasse.
In dieser Controller-Klasse hälst du intern eine Liste/Map mit Name/TParentDeviceClass Mapping und zwei public Methoden:

procedure TDeviceController.RegisterClass(const aName: String; const aClass: TParentDeviceClass);
und
TDeviceController.GetClass(const aName: String): TParentDeviceClass);

Im <initialization> Abschnitt der einzelnen Units registrierst du einfach mittels TDeviceController.RegisterClass('TypX', TDevicveClassX) die jeweilige Klasse.
Uwe's "class property DeviceClass" übergibst du dann den String unter dem die Klasse registriert wurde anstelle des Enums und holst dir im Getter die konkrete Klasse über TDeviceController.GetClass(aName).

Damit sparst du dir den Enum, die zirkulären Unit Referenzen und musst die TParentDevice Klasse nicht jedesmal erweitern wenn mal eine DeviceClass hinzu kommt.
VG

Uwe Raabe 28. Jul 2022 10:59

AW: Abgeleitete Klassen auf einzelne Units verteilen
 
Zitat:

Zitat von pmuetze (Beitrag 1509402)
dass der TParentDevice die einzelnen abgeleiteten TDeviceClasses kennen (und deren Units per zirkulärer Unit-Referenz einbinden) muss ist meiner Meinung nach ein Anti-Pattern und schlechtes Design.

Anti-Pattern würde ich das nicht nennen. Ich hatte ganz bewusst zunächst auf die Auslagerung der Child-Klassen verzichtet und sie erstmal nur im Implementation Teil platziert. Damit sind sie von außen nicht sichtbar. Die Verlagerung in separate Units verläuft dann vollkommen transparent und unsichtbar für den Rest der Anwendung. Auf die Problematik der daraus entstehenden zirkulären Referenzen hatte ich bereits hingewiesen und diese auch bewertet. Hier muss man zwischen Pragma und Dogma abwägen.

Zitat:

Zitat von pmuetze (Beitrag 1509402)
Spendiere der Unit mit dem TParentDevice noch eine statische TDeviceController Klasse.

Das würde ich dann sogar gleich in TParentDevice mit class methods realisieren, anstatt den Umweg über eine zusätzliche Klasse zu nehmen.

Allerdings gibt es bei dem Registrierungs-Verfahren noch ein paar Fallstricke.

Zum einen finde ich die Zuordnung über den Typ als string nicht wirklich glücklich. Auf den Enum würde ich nur ungern verzichten, schon wegen der Kompatibilität zum aktuellen Code. Die möglichen Typ-Strings sollten dann ja auch zumindest als Konstanten deklariert werden und dann muss bei einer Erweitung eben doch diese oder eine anderen Unit mit diesen Konstanten angepasst werden. Das ist also kein wirklicher Vorteil von strings und dazu ist der Enum auch noch type-safe.

Zum anderen muss auch behandelt werden, wenn sich zwei Klassen auf denselben Typ oder auf einen Typ keine Klasse registriert. Das ist zwar alles machbar, aber von der Implementierung her doch etwas aufwändiger als eine simple case-Anweisung.

Als drittes muss man noch beachten, dass die Units mit den Child-Klassen ja von keiner anderen direkt verwendet werden und somit entweder explizit in das oder die jeweiligen (auch zukünftige) Projekte aufgenommen werden müssen oder in einer separaten Registrierungs-Unit in der Uses aufzulisten sind, damit sie auch am Ende in der EXE landen.

Und in einem halben Jahr kommt dann eine neue Child-Klasse/Unit dazu und alle Projekte müssen angepasst werden.

In der pragmatischen Variante mit den Mini-Zyklen muss lediglich die Enumeration erweitert werden, die implementation Uses-Anweisung ergänzt und die case-Anweisung erweitert werden. Damit profitieren automatisch alle Projekte die uParentDevice verwenden von der neuen Child-Klasse und die betreffenden Anwender-Units können den neuen Enum-Wert verwenden. Der erweiterte Enum ist übrigens die einzige Änderung, die von außen sichtbar ist - und das ist ja auch so gewollt.

Ich finde das schon eine signifikante Erleichterung zu dem Registrierungs-Ansatz. In anderen Szenarien hat dieser seine Berechtigung, aber hier sehe ich das nicht so.

Es ist sicher hinlänglich bekannt, dass ich ein erklärter Gegner von zirkulären Referenzen bin. Andererseits gilt aber auch: Keine Regel ohne Ausnahme!


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:03 Uhr.
Seite 1 von 2  1 2      

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