AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren

Anfängerfragen zu Klassenaufbau

Ein Thema von norwegen60 · begonnen am 4. Nov 2017 · letzter Beitrag vom 5. Nov 2017
Antwort Antwort
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
504 Beiträge
 
Delphi 12 Athens
 
#1

Anfängerfragen zu Klassenaufbau

  Alt 4. Nov 2017, 10:07
Hallo zusammen,

ich bin dabei mich verstärkt in die Verwendung von Klassen einzuarbeiten und stoße immer wieder auf Unsicherheiten. Ich habe mal folgenden Klassenaufbau implementiert:
Delphi-Quellcode:
unit Unit5;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Generics.Collections, Vcl.StdCtrls;

type
  // Aufbau der Klassenstruktur
  // ...TTest
  // ....└─ TTestplan
  // ........└─ THardware
  // ............└─ THardwareList (TObjectList<THardwareBase>)
  // ................├─ THardwarePower (THardwareBase)
  // ................|...├─ TConfigPower (TConfigBase)
  // ................|...└─ TProcPower
  // ................└─ THardwareSpeed (THardwareBase)
  // ....................├─ TConfigSpeed (TConfigBase)
  // ....................└─ TProcSpeed

  TForm1 = class(TForm)
    btReadPower: TButton;
    Label1: TLabel;
    btCreateTest: TButton;
    btCreatePower: TButton;
    procedure btReadPowerClick(Sender: TObject);
    procedure btCreateTestClick(Sender: TObject);
    procedure btCreatePowerClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

type
  TConfigBase = class
    ID : Integer;
    Name : String;
    Port : String;
    constructor Create;
    destructor Destroy; override;
  end;

  TConfigPower = class(TConfigBase)
    PowerRange : String;
  end;

  TConfigSpeed = class(TConfigBase)
    SpeedRange : String;
  end;

  TProcPower = class
    function GetPower:Real;
    constructor Create;
    destructor Destroy; override;
  end;

  TProcSpeed = class
    function GetSpeed: Real;
    constructor Create;
    destructor Destroy; override;
  end;

  THardwareBase = class
    Name : String;
    constructor Create;
    destructor Destroy; override;
  end;

  THardwarePower = class(THardwareBase)
    Config : TConfigPower;
    Proc : TProcPower;
    constructor Create;
    destructor Destroy; override;
  end;

  THardwareSpeed = class(THardwareBase)
    Config : TConfigSpeed;
    Proc : TProcSpeed;
    constructor Create;
    destructor Destroy; override;
  end;

  THardwareList = class(TObjectList<THardwareBase>)
  public
    function GetHardwarePower: THardwarePower;
    function GetHardwareSpeed: THardwareSpeed;
  end;

  THardware = class
    Name : String;
    HardwareList : THardwareList;
    constructor Create;
    destructor Destroy; override;
  end;

  TTestplan = class
    Name :String;
    Hardware : THardware;
    constructor Create;
    destructor Destroy; override;
  end;

  TTest = class
    Name :String;
    Testplan : TTestplan;
    constructor Create;
    destructor Destroy; override;
  end;

var
  Form1: TForm1;
  Test : TTest;

implementation

{$R *.dfm}

{ TConfigBase }

constructor TConfigBase.Create;
begin

end;

destructor TConfigBase.Destroy;
begin

  inherited;
end;

{ TProcPower }

constructor TProcPower.Create;
begin

end;

destructor TProcPower.Destroy;
begin

  inherited;
end;

function TProcPower.GetPower: Real;
begin

end;

{ TProcSpeed }

constructor TProcSpeed.Create;
begin

end;

destructor TProcSpeed.Destroy;
begin

  inherited;
end;

function TProcSpeed.GetSpeed: Real;
begin

end;

{ THardwareBase }

constructor THardwareBase.Create;
begin

end;

destructor THardwareBase.Destroy;
begin

  inherited;
end;

{ THardwarePower }

constructor THardwarePower.Create;
begin
  Config := TConfigPower.Create;
  Proc := TProcPower.Create;
end;

destructor THardwarePower.Destroy;
begin
  Config.Free;
  Proc.Free;

  inherited;
end;

{ THardwareSpeed }

constructor THardwareSpeed.Create;
begin
  Config := TConfigSpeed.Create;
  Proc := TProcSpeed.Create;
end;

destructor THardwareSpeed.Destroy;
begin
  Config.Free;
  Proc.Free;

  inherited;
end;

{ THardwareList }

function THardwareList.GetHardwarePower: THardwarePower;
var
  lObject: TObject;
begin
  Result := nil;
  for lObject in self do
    if lObject is THardwarePower then
    begin
      Result := lObject as THardwarePower;
      break;
    end;
end;

function THardwareList.GetHardwareSpeed: THardwareSpeed;
begin

end;

{ THardware }

constructor THardware.Create;
begin
  HardwareList := THardwareList.Create(true);
end;

destructor THardware.Destroy;
begin
  HardwareList.Free;

  inherited;
end;

{ TTestplan }

constructor TTestplan.Create;
begin

end;

destructor TTestplan.Destroy;
begin
  Hardware.Free;

  inherited;
end;

{ TTest }

constructor TTest.Create;
begin

end;

destructor TTest.Destroy;
begin
  Testplan.Free;

  inherited;
end;

procedure TForm1.btCreateTestClick(Sender: TObject);
begin
  if not assigned(Test) then
  begin
    Test := TTest.Create;
    Test.Testplan := TTestplan.Create;
    Test.Testplan.Hardware := THardware.Create;
  end;
end;

procedure TForm1.btReadPowerClick(Sender: TObject);
begin
  if assigned(Test) and
     assigned(Test.Testplan) and
     assigned(Test.Testplan.Hardware) and
     assigned(Test.Testplan.Hardware.HardwareList) and
     (Test.Testplan.Hardware.HardwareList.GetHardwarePower <> nil) then
       Label1.Caption := Test.Testplan.Hardware.HardwareList.GetHardwarePower.Name
  else
    Label1.Caption := 'Keine Leistungsmessgerät gefunden';
end;

procedure TForm1.btCreatePowerClick(Sender: TObject);
var
  HardwarePower : THardwarePower;
begin
  HardwarePower := THardwarePower.Create;
  HardwarePower.Name := 'Fluke 300';
  Test.Testplan.Hardware.HardwareList.Add(HardwarePower);
end;

end.
Dieser Aufbau ist stark vereinfacht, weshalb man nicht zuviel Zeit in Sinn oder Unsinn stecken sollte. In der Realklasse sind auch private/public-Bereiche und Properties verwendet. Das wichtige ist mir die Struktur der Klassen mit Unterklassen, und die THardwareList, die unterschiedliche Objekte aufnehmen kann. Jedes Objekt kann dabei nur einmal vorkommen. Es müssen aber nicht alle möglichen Objekte in der Liste vorhanden sein. Wie im Beispiel oben, wo nur HardwarePower in der Liste enthalten ist.

Meine Frage sind:
  • Wie kann man in der Abfrage btReadPower vermeiden, dass für jede Instanz erst abgefragt werden muss, ob sie erstellt wurde? In der Realklasse könnte es sein, dass z.B. keine Hardware definiert wurde.
  • Muss man THardwarePower ein eigenes Create erstellen oder reicht es aus, das von THardwareBase aufzurufen
  • Welche Unterschiede ergeben sich, ob ich THardwareList = class(TObjectList<THardwareBase>) oder THardwareList = class(TObjectList<TObject>) definiere?
  • Gibt es grundsätzliche Fehler im Aufbau?

Vielen Dank für eure Unterstützung
Gerd

Geändert von norwegen60 ( 4. Nov 2017 um 10:11 Uhr)
  Mit Zitat antworten Zitat
mensch72

Registriert seit: 6. Feb 2008
838 Beiträge
 
#2

AW: Anfängerfragen zu Klassenaufbau

  Alt 4. Nov 2017, 14:34
...Welche Unterschiede ergeben sich, ob ich THardwareList = class(TObjectList<THardwareBase>) oder THardwareList = class(TObjectList<TObject>) definiere?"...
Über die Elemente eine reinen TObjectList kannst du nicht ohne Cast weiter einfach nach innen per ".???" zugreifen.

Trotzdem würd ich als keine Vereinfachung in deinem Kontept die separate Listenklasse sparen und die Liste direkt in THardware aanlegen, also so:

Delphi-Quellcode:
  THardware = class
    Name : String;
    HardwareList : TObjectList<THardwareBase>;
    constructor Create;
    destructor Destroy; override;

    property Count:Integer read GetHardwareListCount;
    property Value[idx:Integer]:THardwareBase read GetHardwareListValue; default;
  end;
so tiefer Direktzugriff ist nie gut... und hier auch noch logisch falsch weil du kein Element von deiner "Hardwarelist" ausgewält hast!?
statt "Test.Testplan.Hardware.HardwareList[?].GetHardwarePower.Name" besser so allgemeines per Property in Hardware sicher lösen, wo es zur Vorabfrage auch einen Count gibt.

also
"if Test.Testplan.Hardware.HardwareList.Count>0 then Caption:=Test.Testplan.Hardware.HardwareList[0].Name"

oder besser wären CountProperty und ein virtuelles ArrayProperty(als "default") in THardware, so dass dann dies so funktioniert ala "Test.Testplan.Hardware.Count"
"if Test.Testplan.Hardware.Count>0 then Caption:=Test.Testplan.Hardware[0].Name"

Rein logisch sehe ich mit aktuellen Angaben auch noch keinen Grund für Test->Testplan.. ausser es kann/soll mal innerhalb eines Tests mehrere Testpläne geben?
Ich würde aber eher in Richtung Testplan->Tests denken, also das ein Testplan mehrere Tests beinhalten kann, dann würde ähnlich "THardware" TTestplan eine TObjecvtlist<TTest> bekommen und auch wieder eine Count und eine Arrayproperty...

"if (Testplan.Count>0) and (Testplan[0].Hardware.Count>0) then Caption:=Testplan[0].Hardware[0].Name"
...das sähe dann für mich schon viele schöner und praktischer aus und du sparst dir die Assigned-Kette weil es ist immer bis zur Liste alles vorhanden(legt man im "contruktor" an und gibt es im "destruktor" frei), nur ob Elemente in der Liste ist per ".count" zu prüfen wenn direkt auf ein Element zugegriffen werden soll.
...bei Zugriffen auf undefinierte Elemente kannst du so selbst entscheiden kontrolliert eine Exeption zu werfen, oder per eigenem Defaultelement für undefined/outofrange eine gültig Standardrückgabe erzeugen.

Geändert von mensch72 ( 4. Nov 2017 um 14:48 Uhr)
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
504 Beiträge
 
Delphi 12 Athens
 
#3

AW: Anfängerfragen zu Klassenaufbau

  Alt 5. Nov 2017, 08:23
Hallo mensch72,

zuerst muss ich vielleicht dazu sagen, dass manche Dinge in dem Projekt, an dem ich mitarbeite, schon gegegen sind. Dazu gehört die Struktur bis runter zu THardware. Im Moment ist dann aber alles was man Hardwareinformationen braucht, in eine riesiges, flaches THardware-Element gepackt. Jetzt wollten wir eigene Elemente für jedes Messgerät erstellen.

so tiefer Direktzugriff ist nie gut... und hier auch noch logisch falsch weil du kein Element von deiner "Hardwarelist" ausgewält hast!?
statt "Test.Testplan.Hardware.HardwareList[?].GetHardwarePower.Name" besser so allgemeines per Property in Hardware sicher lösen, wo es zur Vorabfrage auch einen Count gibt.

also
"if Test.Testplan.Hardware.HardwareList.Count>0 then Caption:=Test.Testplan.Hardware.HardwareList[0].Name"
Grundsätzlich hatte und habe ich auch meine Probleme mit diesen tiefen Verschachtelungen. Aber nach vielen Diskussionen mit unseren C#-Kollegen und nachdem dieses Projekt schon so begonnen wurde, suchen wir einen Weg, um das fortzusetzen.

In deiner Änderung liegt glaube ich ein Missverständnis vor. Die HardwareList kann diverse Messgeräte enthalten. z.B. eins für die Leistungsmessung, eins für die Geschwindigkeits- und Wegmessung, ... Deshalb greife ich nicht über HardwareList[0].Name auf ein Element zu, sondern lasse mir das passende Element über die Funktionen in THardwareList (GetHardwarePower, GetHardware,Speed, ...) ermitteln.

Rein logisch sehe ich mit aktuellen Angaben auch noch keinen Grund für Test->Testplan.. ausser es kann/soll mal innerhalb eines Tests mehrere Testpläne geben?
Test enthält das Testprotokoll.
Testplan: Enthält enthält was mit welcher Toleranz und welchem Messmittel zu testen ist
Test: Ist ein abgeschlossener Test für eine Charge und enthält, mit welchem Testplan gemessen werden soll und die Messresultate

"if (Testplan.Count>0) and (Testplan[0].Hardware.Count>0) then Caption:=Testplan[0].Hardware[0].Name"
...das sähe dann für mich schon viele schöner und praktischer aus und du sparst dir die Assigned-Kette weil es ist immer bis zur Liste alles vorhanden(legt man im "contruktor" an und gibt es im "destruktor" frei), nur ob Elemente in der Liste ist per ".count" zu prüfen wenn direkt auf ein Element zugegriffen werden soll.
Daran, alle Elemente immer im Constructor anzulegen, habe ich auch schon gedacht. Da die Struktur über THardware aber Altlasten sind, ist das im Moment nur zu 99.99% so. Wenn alles richtig läuft, sind die ganzen Elemente angelegt.
Beispiel für Ausnahme: Aus irgendeinem Grund wurde eine Hardware in der Datenbank gelöscht nachdem ein Test angelegt wurde. Ganz klar, dürfte nie passieren, aber in der DB gibt es keine Constraints, die das verhindern
Bei THardware gibt es noch eine andere Einschränkung: Über TTest gibt es in Wirklichkeit noch TBatch. TBatch ist eine Liste von TTest, die durchgeführt werden sollen. Da verschiedene TTest mit der gleichen THardware arbeiten könnten, wird THardware nicht in jedem TTest created sondern liegt in einer THardwareList. Bei gleicher THardware zeigen also zwei TTest auf das gleichen THardware-Elemente. Da habe ich dann schon mehr Skrupel, in der THardwareListe ein Leerdummy zu erstellen.


Grüße
Gerd
  Mit Zitat antworten Zitat
Aviator

Registriert seit: 3. Jun 2010
1.610 Beiträge
 
Delphi 10.3 Rio
 
#4

AW: Anfängerfragen zu Klassenaufbau

  Alt 5. Nov 2017, 11:27
Ohne mich jetzt ganz tief mit deinem Beispiel Source (natürlich habe ich ihn mir angeschaut ) beschäftigt zu haben werfe ich mal noch die Stichworte Interfaces und - für euch dann auch ganz gut - Factories in den Raum. Zumindest würde ich das für Sinnvoll empfinden, wenn ich mir deine folgende Aussage durchlese:

Zitat von norwegen60:
In deiner Änderung liegt glaube ich ein Missverständnis vor. Die HardwareList kann diverse Messgeräte enthalten. z.B. eins für die Leistungsmessung, eins für die Geschwindigkeits- und Wegmessung, ... Deshalb greife ich nicht über HardwareList[0].Name auf ein Element zu, sondern lasse mir das passende Element über die Funktionen in THardwareList (GetHardwarePower, GetHardware,Speed, ...) ermitteln.
Jeder Test implementiert also das Interface ITest . Dann gibt es noch eine Factory, die dir anhand des Namens oder einer Nummer oder sonst etwas des zu testenden Gerätes die entsprechende Testklasse zurückliefert. Und dann wird euer Code gleich etwas abstrakter, einfacher zu warten und er ist leichter durch neue Tests erweiterbar. Somit würden später dann an wahrscheinlich sehr vielen Stellen auftauchende If-Then-Else Konstrukte verschwinden. Dieses If-Then-Else Konstrukt wäre dann nur noch einmal vorhanden. Und zwar in der Factory.

Wenn dann verschiedene Geräte tiefer gehende Tests benötigen, dann gibt es noch ein erweitertes Interface ITestSpeed welches dann von der betreffenden Klasse implementiert wird. Das Interface selbst kann dann noch von ITest abgeleitet werden. Also so: Interface ITestSpeed(Test)
  Mit Zitat antworten Zitat
mensch72

Registriert seit: 6. Feb 2008
838 Beiträge
 
#5

AW: Anfängerfragen zu Klassenaufbau

  Alt 5. Nov 2017, 13:30
..."Über TTest gibt es in Wirklichkeit noch TBatch. TBatch ist eine Liste von TTest,"... (wieder die Frage wirklich ?IST? oder ?ENTHÄLT/VERWALTET? oder ?BENUTZT? eine Liste von TTest)

..."Da verschiedene TTest mit der gleichen THardware arbeiten könnten, wird THardware nicht in jedem TTest created sondern liegt in einer THardwareList. Bei gleicher THardware zeigen also zwei TTest auf das gleichen THardware-Elemente"...
=> Sorry aus deiner anfangs als Vorgabe aufgezeigten Klassenhirarchie erschließt sich das zuletzt geschriebene nicht, daher war aktuell jeder Vorschlag zur Klassenstruktur im Prinzip reines Glaskugel reiben...

-> male es als VOLLSTÄNDIGE Struktur und beschreibe deren reale Nutzung im Programm, also auch die Logik wer Eigentümer und Verwalter von Listenobjekten ist, das ist ja oft nicht der welcher ein Objekt zu einer Liste hinzufügt, vor allem wenn es ein globales "mehrfach nutzbares" Objekt geht.
-> achte diesmal sehr genau auf deine Formulierung zu "Listen"... "ist eine Liste von X","besitzt/verwaltet eine Liste von X" oder "oder enthält eine Liste von externen X" ist jeweils was total anderes! (Bei der sauberen Lösung hilft das Vermeiden simpler Listenklassen als "ist reine StandardListe von X", da die interne Verwaltung speziell beim Anlegen und Löschen bei jeder Nutzung hier überall und extra separat programmiert werden muss)
-> Wenn du UML kannst, male und schreibe es in UML Syntax, wenn dir SQL besser liegt erzeuge mal ein Datenmodell in 3. Normalform, welches alles redunanzfrei abbildet (braucht du eigentlich eh, wenn du Anfangs deine Setups und Vorgaben einlesen und anschließend deine Resultate abspeichern willst)
-> wenn du das hast, ergibt sich die Klassenstruktur automatisch wenn du einen guten Weg findest, die sagen wir "globalen" CoreObjekte all deinen Klassen sauber zur Verfügung zu stellen... manche machen das über (mehrere) Interfaces, ich würde im einfachstem Fall in jedem Konstruktor (m)einen CoreContainer(der alles globale enthält und verwaltet) übergeben

Mehr wie diese paar allgemeinen Sachen kann ich da erstmal nicht mehr sagen.
  Mit Zitat antworten Zitat
Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:05 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