Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Best Practice Aggregation von Informationen (https://www.delphipraxis.net/184058-best-practice-aggregation-von-informationen.html)

alda 22. Feb 2015 15:01


Best Practice Aggregation von Informationen
 
Hallo zusammen,

ich bin gerade etwas am rumspielen und würde mir gerne Eure Meinungen und Vorschläge zu einem bestimmten Thema einholen.
Im Detail beschäftige ich mich gerade mit der Gestaltung einer Tabellenkomponente.

Vorab schonmal sorry für die Beitragslänge, ich konnte aber irgendwie auch kein Spoiler-Tag ausfindig machen um das ganze leserlicher zu gestalten ;-( Wo find ich das denn ?

Folgendes Szenario:
Eine Tabelle besteht wie gewohnt aus einem Layout von Spalte (Column), Zeile (Row) und Zelle (Cell).
Diese drei Elemente müssen je nach Vorstellung die verschiedensten Eigenschaften bereitstellen, wie z.B. Möglichkeiten zur Selektion, Lokalisierung, Sichtbarkeit und Identifizierung.

Um im weiteren Entwicklungsverlauf flexibel zu bleiben, war die Idee aus den genannten Eigenschaften je eine Schnittstelle bereitgestellt (GUIDs habe ich fürs Lesen mal weggelassen):
Delphi-Quellcode:
  ISelectable = interface(IInvokable)
    procedure Select;
    procedure Deselect;
    function IsSelected;
  end;

  ILocatable = interface(IInvokable)  
    function Coordinate: TCoordinate;
  end;

  IDisplayable = interface(IInvokable)  
    function IsVisible: boolean;
  end;

  IIdentifiable = interface(IInvokable)  
    function UUID: String;
  end;
Wo ich mir nun absolut unschlüssig bin, ist das Bereitstellen der Eigenschaften (Selektion, Lokalisierung, Sichtbarkeit und Identifizierung) für Zeile, Spalte und Zelle - unter der Voraussetzung, dass Zeile, Spalte und Zelle InterfacedObjects bleiben. Wie würdet Ihr die Interfaces gestalten, oder was würdet Ihr komplett anders machen?:
Delphi-Quellcode:
 IRow = interface(IInvokable)                  
  end;

  IColumn = interface(IInvokable)                  
  end;

  ICell = interface(IInvokable)                  
  end;

1. die Interfaces der Elemente bei jeder weiteren Eigenschaft erweitern und fest verdrahten?

Interfaces:
Delphi-Quellcode:
  IRow = interface(IInvokable)  
    function Selectable: ISelectable;
    function Locatable: ILocatable;
    function Displayable: IDisplayable;
    function Identifiable: IIdentifiable;
  end;

  IColumn = interface(IInvokable)  
    function Selectable: ISelectable;
    function Locatable: ILocatable;
    function Displayable: IDisplayable;
    function Identifiable: IIdentifiable;
  end;

  ICell = interface(IInvokable)  
    function Selectable: ISelectable;
    function Locatable: ILocatable;
    function Displayable: IDisplayable;
    function Identifiable: IIdentifiable;
  end;
Verwendung also:
Delphi-Quellcode:
....
var
  LRow: IRow;
begin
 DoThingsWithUUID( LRow.Identifiable.UUID );
end;
....

2. die Interfaces der Elemente nur Element-Spezifische Infos tragen lassen, und die allgemeinen Eigenschaften der Implementierung überlassen und casten?

Interfaces:
Delphi-Quellcode:
  IRow = interface(IInvokable)
    procedure DoThingsOnlyRowsDo;                
  end;

  IColumn = interface(IInvokable)                  
    procedure DoThingsOnlyColumnsDo;
  end;

  ICell = interface(IInvokable)
    procedure DoThingsOnlyCellsDo;                  
  end;
Beispielimplementierung:
Delphi-Quellcode:
...
TRow = class(TInterfacedObject,
                        IRow,
                        IIdentifiable,
                        IDisplayable,
                        ILocatable)
...
Verwendung also:
Delphi-Quellcode:
....
var
  LRow: IRow;
begin
 DoThingsWithUUID( (LRow AS IIdentifiable).UUID );
end;
....

3. Aggregation der Informationen über eine Art Extension?

Interfaces:
Delphi-Quellcode:
  IRow = interface(IInvokable)
    procedure DoThingsOnlyRowsDo;                
  end;

  IColumn = interface(IInvokable)                  
    procedure DoThingsOnlyColumnsDo;
  end;

  ICell = interface(IInvokable)
    procedure DoThingsOnlyCellsDo;                  
  end;
Beispielimplementierung:
Delphi-Quellcode:
...
TRow = class(TInterfacedObject,
                        IRow,
                        IIdentifiable,
                        IDisplayable,
                        ILocatable)
...
Extension:
Delphi-Quellcode:
  Row = record
  private
    fThis: IRow;
    function GetThis: IRow;
  public
    constructor Create(const AValue: IRow);

    class operator Implicit(const value: Row): IRow;

    function AsIdentifiable: IIdentifiable;
    function AsDisplayable: IDisplayable;
    function AsLocatable: ILocatable;
  end;

Verwendung also:
Delphi-Quellcode:
procedure DoThingsWithRow(const ARow: IRow);
var
  LRow: Row;
begin
 LRow.Create(ARow);
 DoThingsWithUUID( LRow.AsIdentifiable.UUID );
end;
....

stahli 22. Feb 2015 15:45

AW: Best Practise Aggregation von Informationen
 
Ey Alda, ;-)

ich würde in Deinem Fall überlegen, ob Dir Interfaces hier etwas bringen.

Du willst offenbar eine fixe Gitterkomponente bauen. Interfaces würden Dir ermöglichen, die aktuellen Zellen irgendwann durch andere Klassen zu ersetzen. Außerdem würden die Zellen automatisch freigegeben, wenn sie nicht mehr genutzt werden.
Bei einer Gitterkomponente würde ich erst mal denken, dass beides nicht unbedingt notwendig ist. Der Gitterkomponente können ja ruhig alle TRow, TColumn und TCell bekannt sein und sie kann die entsprechenden Objekte in eigener Kontrolle erzeugen, verwalten und freigeben.

Also Interfaces sind möglicherweise nicht wirklich hilfreich!?
(Und warum IInvokable statt IInterface?)

OK, wenn Interfaces, dann würde ich Weg 2 gehen.
Es handelt sich hier um direkte Eigenschaften der Klassen. ISelectable wird unterstützt, wenn es der Klasse zugewiesen ist. Fertig.

Im Weg 1 müsstest Du ein Objekt erzeugen und es ISelectable zuweisen. Sonst wäre es nil.
Das würde ich so anwenden, wenn man z.B. ein Auto zusammenbaut. Lenker und Motor können dann als Instanzen verwaltet werden. Auf dem Fließband ist der Motor zunächst noch nil. Erst zum Schluss wird eine Motorinstanz eingebaut.

Ich fände das für Deinen Anwendungsfall zu umständlich und unpassend.

Weg 3 würde ich ausschließen. Warum willst Du auf einmal mit Records arbeiten. (Kann sein, dass das machbar ist, aber das erscheint mir erst mal nicht ganz plausibel).

alda 22. Feb 2015 16:28

AW: Best Practise Aggregation von Informationen
 
Zitat:

Zitat von stahli (Beitrag 1290990)
Du willst offenbar eine fixe Gitterkomponente bauen. Interfaces würden Dir ermöglichen, die aktuellen Zellen irgendwann durch andere Klassen zu ersetzen. Außerdem würden die Zellen automatisch freigegeben, wenn sie nicht mehr genutzt werden.
Bei einer Gitterkomponente würde ich erst mal denken, dass beides nicht unbedingt notwendig ist. Der Gitterkomponente können ja ruhig alle TRow, TColumn und TCell bekannt sein und sie kann die entsprechenden Objekte in eigener Kontrolle erzeugen, verwalten und freigeben.

Das würde bedeuten, dass jemand Drittes keine Möglichkeit hätte die Default-Implementierungen von Spalte, Zeile und Zelle zu tauschen, außer er leitet von den Defaultklassen ab - und das bedeutet er muss das Verhalten ändern / überschreiben.

Zitat:

Zitat von stahli (Beitrag 1290990)
(Und warum IInvokable statt IInterface?)

Für Interception (Mocks), dann spar ich mir das $M+.

Zitat:

Zitat von stahli (Beitrag 1290990)
OK, wenn Interfaces, dann würde ich Weg 2 gehen.
Es handelt sich hier um direkte Eigenschaften der Klassen. ISelectable wird unterstützt, wenn es der Klasse zugewiesen ist. Fertig.

Genau da bin ich mir unschlüssig. So ist für jemand Dritten nicht sofort klar welche Interfaces z.B. eine Zellenimplementierung unterstützen muss um reibungslos in der Tabellenkomponente zu funktionieren (ein Cast schlägt z.B. fehl). Also angenommen er würde die Implementierung hinter ICell austauschen wollen. Oder nicht?

Zitat:

Zitat von stahli (Beitrag 1290990)
Im Weg 1 müsstest Du ein Objekt erzeugen und es ISelectable zuweisen. Sonst wäre es nil.
Das würde ich so anwenden, wenn man z.B. ein Auto zusammenbaut. Lenker und Motor können dann als Instanzen verwaltet werden. Auf dem Fließband ist der Motor zunächst noch nil. Erst zum Schluss wird eine Motorinstanz eingebaut.

Die Implementierung setzt natürlich voraus, dass die Eigenschaften niemals NIL sind.

Zitat:

Zitat von stahli (Beitrag 1290990)
Weg 3 würde ich ausschließen. Warum willst Du auf einmal mit Records arbeiten. (Kann sein, dass das machbar ist, aber das erscheint mir erst mal nicht ganz plausibel).

Die Idee war, in der Tabellenkomponente immer über Records auf die Instanzen von Row, Cell und Column zuzugreifen und dort alle untersützten Interfaces zu konsolidieren. Aber das gefällt mir auch nicht, da stimme ich Dir zu :D

BUG 23. Feb 2015 00:29

AW: Best Practice Aggregation von Informationen
 
Dein Problem resultiert ein bisschen daraus, dass Delphi/COM-Interfaces keine Mehrfachvererbung unterstützen. Ansonsten könntest du dir einfach ein "Bündel" von zu unterstützenden Interfaces schnüren. (siehe auch)
Von deinen Lösungen ist Lösung 1 an dieser Sichtweise nächsten dran.

Eine andere Variante wäre es, eine abstrakte Basisklasse zu erstellen, die alle nötigen Interfaces besitzt. Wenn jemand unbedingt eine andere Basis braucht, muss er halt einen Adapter schreiben.

alda 25. Feb 2015 09:04

AW: Best Practice Aggregation von Informationen
 
Zitat:

Zitat von BUG (Beitrag 1291044)
Dein Problem resultiert ein bisschen daraus, dass Delphi/COM-Interfaces keine Mehrfachvererbung unterstützen. Ansonsten könntest du dir einfach ein "Bündel" von zu unterstützenden Interfaces schnüren. (siehe auch)
Von deinen Lösungen ist Lösung 1 an dieser Sichtweise nächsten dran.

Eine andere Variante wäre es, eine abstrakte Basisklasse zu erstellen, die alle nötigen Interfaces besitzt. Wenn jemand unbedingt eine andere Basis braucht, muss er halt einen Adapter schreiben.

Ich werd auch erstmal mit Variante 1 starten denk ich, da diese Infos essentiell sind und einem viele Supports() Aufrufe ersparen. Weitere Meinungen sind aber trotzdem gerne Willkommen ;-)


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