Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Aufbau eigene Klasse mit Property und TStrings (https://www.delphipraxis.net/157115-aufbau-eigene-klasse-mit-property-und-tstrings.html)

Jens Hartmann 30. Dez 2010 11:51

Aufbau eigene Klasse mit Property und TStrings
 
Hallo zusammen,

in folgendem Thread habe ich nach dem Aufbau einer Anwendung/Dienst gefragt.

Dienst oder Anwendung

Ich Versuche die Sache gerade mit dem Datenmodul und einer eigene Klasse zu lösen. Jetzt möchte ich einfach mal wissen, ob folgender Aufbau der unten gezeigten Klasse in Ordnung ist. Da ich sowas bislang noch nicht gemacht habe, bin ich mir z.B. mit dem Aufbau von..
Delphi-Quellcode:
constructor TMB100.create;
begin
  inherited;
  FComPortProperty := TStringlist.Create;
  FComPortProperty.Add('Test');
  FComPortProperty.Add('Test 2');
end;
//und
destructor TMB100.destroy;
begin
  FComPortProperty.Free;
  inherited;
end;
nicht sicher, ob ich dort meine TStringliste.Create und .Free einbinden kann. Desweiteren würde ich mal gerne wissen, ob ich über die
Delphi-Quellcode:
Property
nicht auch einen Record übergeben kann. Dies wäre den Daten die ich übergeben will näher.

Hier mal die gesamte Klasse...

Delphi-Quellcode:
unit mb100parser;

interface

uses
  Classes, Graphics;

  type
    TMB100 = class
      constructor create;
      destructor destroy;
    private
      FFarbe : array [1..4] of TColor;
      FComPortProperty : TStrings;
      function GetComPortPropertys : TStrings;
      procedure SetColor(Nr : integer; SetFarbe : TColor);
      function GetColor(Nr : integer) : TColor;
    public
      property ComPortPropertys : TStrings read GetComPortPropertys write FComPortProperty;
      property Farbe[Nr : integer] : TColor read GetColor write SetColor;
    end;

implementation

constructor TMB100.create;
begin
  inherited;
  FComPortProperty := TStringlist.Create;
  FComPortProperty.Add('Test');
  FComPortProperty.Add('Test 2');
end;

destructor TMB100.destroy;
begin
  FComPortProperty.Free;
  inherited;
end;

function TMB100.GetComPortPropertys : Tstrings;
begin
  Result := FComPortProperty;
end;

procedure TMB100.SetColor(Nr : integer; SetFarbe : TColor);
begin
  FFarbe[Nr] := SetFarbe
end;

function TMB100.GetColor(Nr : integer) : TColor;
begin
  Result := FFarbe[Nr];
end;
end.
Vielen dank schon mal und Gruß Jens

Thom 30. Dez 2010 12:35

AW: Aufbau eigene Klasse mit Property und TStrings
 
Das mit der Erstellung der Stringliste im Constructor und die Freigabe im Destructor ist OK.

Allerdings geht
Delphi-Quellcode:
property ComPortPropertys: TStrings read GetComPortPropertys write FComPortProperty;
//
garantiert schief, da Du beim Schreiben der Eigenschaft einfach die erstelle Stringliste überschreibst und es damit zu einem Speicherleck kommt. Normalerweise brauchst du dafür keinen Setter (also wird die Eigenschaft nur read-only). Dennoch kannst Du natürlich schreibend auf den Inhalt der Stringliste zugreifen:
Delphi-Quellcode:
property ComPortPropertys: TStrings read FComPortPropertys;
//
Willst Du unbedingt die Eigenschaft ComPortPropertys schreibbar gestalten, mußt Du das über eine Methode machen:
Delphi-Quellcode:
property ComPortPropertys: TStrings read FComPortPropertys write SetComPortProperty;

procedure TMB100.SetComPortProperty(Value: TStrings);
begin
  FComPortProperty.Assign(Value);
end;
Zum Lesen der Eigenschaft ComPortPropertys brauchst Du übrigens keine Methode (GetComPortPropertys).

r2c2 30. Dez 2010 12:37

AW: Aufbau eigene Klasse mit Property und TStrings
 
- Methodennamen (auch Create und Destroy) werden in Delphi per Konvention Groß geschrieben

Zitat:

nicht sicher, ob ich dort meine TStringliste.Create und .Free einbinden kann.
Das ist genau richtig so.


Zitat:

Desweiteren würde ich mal gerne wissen, ob ich über die
Delphi-Quellcode:
Property
nicht auch einen Record übergeben kann. Dies wäre den Daten die ich übergeben will näher.
Ja das geht und ist manchmal sogar sinnvoll. Meistens eben da, wo die Records Wertetypen darstellen. Tun sie das nicht, denkst du vermutlich noch zu prozedural.


Delphi-Quellcode:
    TMB100 = class
      constructor create;
      destructor destroy;
Die beiden bitte unter public und bei Destroy *immer* override angeben.

Delphi-Quellcode:
      FFarbe : array [1..4] of TColor;
Ich kenn deine Klasse nicht, aber warum genau 4? Haben die Zahlen ne tiefere Bedeutung?


Delphi-Quellcode:
      FComPortProperty : TStrings;
Der Bezeichner scheint mir unglücklich gewählt. Es wird nicht klar, was das darstellt und warum das nun ein TStrings ist.


Delphi-Quellcode:
property ComPortPropertys: TStrings read GetComPortPropertys write FComPortProperty;
Dazu würde ja schon was gesagt.


Delphi-Quellcode:
function TMB100.GetComPortPropertys : Tstrings;
begin
  Result := FComPortProperty;
end;
Das solltest du dir genau überlegen. Du gibst damit die Interne Repräsentation nach außen. Das kann lustige Aliasing-Effekte nach sich ziehen. Sag mal mehr zu der Property.


Delphi-Quellcode:
procedure TMB100.SetColor(Nr : integer; SetFarbe : TColor);
begin
  FFarbe[Nr] := SetFarbe
end;

function TMB100.GetColor(Nr : integer) : TColor;
begin
  Result := FFarbe[Nr];
end;
Was ist, wenn der Index nicht passt?


mfg

Christian

himitsu 30. Dez 2010 12:38

AW: Aufbau eigene Klasse mit Property und TStrings
 
Das write bei deinem StringList-Property weglassen
und wozu der Getter, wenn du eh nur das Feld zurückgibst?

Delphi-Quellcode:
property ComPortProperties: TStrings read FComPortProperty;
.

wenn du unbedingt write haben willst, dann implementier dieses als 'ne Assign-Methode,


Delphi-Quellcode:
property ComPortProperties: TStrings read FComPortProperty write AssignComPortProperties;

procedure TMB100.AssignComPortProperties(Properties: TStrings);
begin
  FComPortProperty.Assign(Properties);
end;
PS: "ie" statt "y", wenn danach z.B. noch ein "s" folgt.

Destroy+Override und das mit der Indexprüfung wurde ja schon gesagt.

sx2008 30. Dez 2010 15:16

AW: Aufbau eigene Klasse mit Property und TStrings
 
Zitat:

Zitat von r2c2 (Beitrag 1071186)
Delphi-Quellcode:
      FComPortProperty : TStrings;
Der Bezeichner scheint mir unglücklich gewählt. Es wird nicht klar, was das darstellt und warum das nun ein TStrings ist.

Hier würde sich in der Tat ein Record anbieten:
Delphi-Quellcode:
TComPortSettings = record
  ComPort : string;  // z.B. 'COM3'
  BaudRate: Integer; // evtl. auch ein Aufzählungstyp
  DataBits: Integer; // 7 oder 8
  Parity:  Char;    // N=None, E=Even, O=Odd oder evtl. auch ein Aufzählungstyp
  ...
end;

    TMB100 = class
...
    public
      property ComPortSettings : TComPortSettings read FComPortSettings write FComPortSettings;
      property Farbe[Nr : integer] : TColor read GetColor write SetColor;
    end;
Wahrscheinlich benützt du eine Komponente für die COM-Ports.
Es kann somit ein "doppel-gemoppelt" Effekt eintreten, denn einerseits werden die Einstellungen für die serielle Schnittstelle von der Klasse TMB100 verwaltet andererseits gibt es eine COM-Port-Komponente, die ebenfalls diese Einstellungen verwaltet.

Die perfekte Lösung wäre also die, dass die Klasse TMB100 die Einstellungen für die serielle Schnittstelle überhaupt nicht zu kennen braucht, sondern sie braucht nur zu wissen welche COM-Port-Komponente sie benützen soll.
Stichwort: Bei Google suchenDependency Injection

Jens Hartmann 30. Dez 2010 15:23

AW: Aufbau eigene Klasse mit Property und TStrings
 
Danke erstmal,
Zitat:

Zitat von r2c2
Zitat:

Zitat von Jens
Desweiteren würde ich mal gerne wissen, ob ich über die Property nicht auch einen Record übergeben kann. Dies wäre den Daten die ich übergeben will näher.

Ja das geht und ist manchmal sogar sinnvoll. Meistens eben da, wo die Records Wertetypen darstellen. Tun sie das nicht, denkst du vermutlich noch zu prozedural.

So, ich habe mal die Klasse angepasst. Zumindest den Constructor und Destructor. In die Klasse kommen später noch einigen Methoden zum Parsen von Ereignissen.
Delphi-Quellcode:
unit mb100parser;

interface

  type
    ComPortProperties = record
      Baudrate, Parität, Flow, Start, Stop, Stopbit, Databit: string;
      CaseInsensitiv, Include : Boolean;
  end;

  type
    TMB100 = class
      Constructor create;
      Destructor destroy; override;
    private
      FComPortProperties : ComPortProperties;
    public
      property ComPort : ComPortProperties read FComPortProperties;
    end;

implementation

Constructor TMB100.create;
begin
  inherited;
  FComPortProperties.Baudrate := '19200';
  FComPortProperties.Parität := 'None';
  FComPortProperties.Flow := 'Hardware';
  FComPortProperties.Start := 'Ereignis';
  FComPortProperties.Stop := #$D#$A#$A;
  FComPortProperties.Databit := '8';
  FComPortProperties.Stopbit := '1';
  FComPortProperties.CaseInsensitiv := true;
  FComPortProperties.Include := true;
end;

Destructor TMB100.destroy;
begin
  inherited;
end;
Zur Zeit nutze ich die Klasse über das Datenmodul. Dieses Datenmodul will ich ja später in die Service-Anwendung integrieren. Hier mal der Aufruf im Datenmodul.

Delphi-Quellcode:
function TDataModuleServer.SetComPort(Hardware, Port : integer) : Boolean;
var
  FMB100 : TMB100;
begin
  try
    Result := false;
    case Hardware of
      0:
        begin
          FMB100 := TMB100.create;
          case Port of
            1: begin
              if ComPortAvailable('COM1') then
                begin
                  ComPort1.Port := 'COM1';
                  ComPort1.BaudRate := StrToBaudRate(FMB100.ComPort.Baudrate);
                  ComPort1.Parity.Bits := StrToParity(FMB100.ComPort.Parität);
                  ComPort1.FlowControl.FlowControl := StrToFlowControl(FMB100.ComPort.Flow);
                  ComPort1.StopBits := StrToStopBits(FMB100.ComPort.Stopbit);
                  ComPort1.DataBits := StrToDataBits(FMB100.ComPort.Databit);
                  ComDataPacket1.StartString := FMB100.ComPort.Start;
                  ComDataPacket1.StopString := FMB100.ComPort.Stop;
                  ComDataPacket1.CaseInsensitive := FMB100.ComPort.CaseInsensitiv;
                  ComDataPacket1.IncludeStrings := FMB100.ComPort.Include;
                  ComPort1.Open;
                  if ComPort1.Connected then
                    Result := true;
                end;
            end;
//.....
  finally
    FMB100.Free;
  end;
end;
Ist der Weg so richtig?

Gruß Jens

Jens Hartmann 30. Dez 2010 15:33

AW: Aufbau eigene Klasse mit Property und TStrings
 
Zitat:

Zitat von sx2008
Die perfekte Lösung wäre also die, dass die Klasse TMB100 die Einstellungen für die serielle Schnittstelle überhaupt nicht zu kennen braucht, sondern sie braucht nur zu wissen welche COM-Port-Komponente sie benützen soll.

Genau das eben nicht. Ich werde später mehrer Klassen schreiben. Je nach angeschlossener und Lizenzierter Hardware, wird die entsprechende Klasse genommen und die Einstellungen der Hardware der ComPort Komponente zugewiesen. Die Anwendung und der Dienst sollen später 4 Ports verarbeiten. In der aktuellen Entwicklungsphase mache ich das zur Zeit folgendermaßen...

Ich habe einen Compilerschalter eingebaut, welcher mir je nach Lizenz die Klassensteuerung übernimmt. Somit bin ich mir sicher, das wirklich nur Lizenzierte Funktionen mit in die Anwendung kompiliert werden.

Delphi-Quellcode:
unit psComServMain;
{$I CompilerSchalter.inc}
//...
type
  THardware = (MB100, MB256, ESSERIQ8, BOSCHUEZ);
//..
procedure TfMain.FormCreate(Sender: TObject);
var
  Hardware : THardware;
begin
  try
    Caption := FormCaption + GetVersionInfos(Application.ExeName);
      {$IFDEF MB100_P1}//Eine Hardware vom Typ MB100 an COM1
        DataModuleServer.SetComPort(0,1);//0=MB100,1=COM1
      {$ENDIF}
Gruß Jens

r2c2 30. Dez 2010 15:54

AW: Aufbau eigene Klasse mit Property und TStrings
 
Zitat:

Zitat von sx2008 (Beitrag 1071241)
Zitat:

Zitat von r2c2 (Beitrag 1071186)
Delphi-Quellcode:
      FComPortProperty : TStrings;
Der Bezeichner scheint mir unglücklich gewählt. Es wird nicht klar, was das darstellt und warum das nun ein TStrings ist.

Hier würde sich in der Tat ein Record anbieten:
Delphi-Quellcode:
TComPortSettings = record
  ComPort : string;  // z.B. 'COM3'
  BaudRate: Integer; // evtl. auch ein Aufzählungstyp
  DataBits: Integer; // 7 oder 8
  Parity:  Char;    // N=None, E=Even, O=Odd oder evtl. auch ein Aufzählungstyp
  ...
end;

Da bin ich mir nicht so sicher. Welchen Vorteil hat der Recird gegenüber dem einbauen in die Klasse?


Zitat:

Die perfekte Lösung wäre also die, dass die Klasse TMB100 die Einstellungen für die serielle Schnittstelle überhaupt nicht zu kennen braucht, sondern sie braucht nur zu wissen welche COM-Port-Komponente sie benützen soll.
Stichwort: Bei Google suchenDependency Injection
Dem schließe ich mich allerdings wieder an. Das wäre *deutlich* besser.

Delphi-Quellcode:
Baudrate, Parität, Flow, Start, Stop, Stopbit, Databit: string;
Warum denn Strings? Dafür gibts Aufzählungstypen!

Delphi-Quellcode:
Destructor TMB100.destroy;
begin
  inherited;
end;
- das Schlüsselwort "descructor" klein, der Bezeichner "Destroy" groß, nicht umgekehrt
- wenn du im Destruktor eh nur inherited aufrufst, brauchst du ihn nicht nochmal zu deklarieren. Er wird ja einfach geerbt

Delphi-Quellcode:
    case Hardware of
      0:
Uaah... Böser Code Smell. Bitte was soll den Hardware=0 sein? So macht man das nicht. Praktischerweise bin ich gerade dabei, einen Blog-Artikel genau über diese Problematik zu schreiben. Wenn der fertig ist (hoffentlich noch heute), sag ich dir Bescheid.


Delphi-Quellcode:
ComPort1.BaudRate := StrToBaudRate(FMB100.ComPort.Baudrate);
Auch so Konstruktionen kommen mir sehr komisch vor. Ich hab aber immer noch nicht ganz verstanden, was du vor hast.

Zitat:

Genau das eben nicht. Ich werde später mehrer Klassen schreiben. Je nach angeschlossener und Lizenzierter Hardware, wird die entsprechende Klasse genommen und die Einstellungen der Hardware der ComPort Komponente zugewiesen. Die Anwendung und der Dienst sollen später 4 Ports verarbeiten. In der aktuellen Entwicklungsphase mache ich das zur Zeit folgendermaßen...
Ich bin mir nicht sicher, ob du verstanden hast, was DependencyIncection ist. Du gewinnst dadurch Flexibilität. "Genau das eben nicht" scheint mir nicht zu stimmen. Im Gegenteil. Erklär mal, was du mit deinen Klassen vor hast. Wie sollen sie sich unterscheiden. Kann sein, dass da schon ein Problem steckt. Ich hab da so ne Ahnung...

mfg

Christian

sx2008 30. Dez 2010 16:25

AW: Aufbau eigene Klasse mit Property und TStrings
 
Ist es nicht so, dass es 4 verschiedene Hardware-Geräte gibt, die alle über die serielle Schnittstelle angesteuert werden?
Sagen wir mal es handelt sich um Alarmanlagen von verschiedenen Herstellern. 8-)
Jedes Hardware-Gerät hat ein etwas anderes Datenprokoll.
Aber die Gemeinsamkeit ist doch, dass immer die serielle Schnittstelle für die Kommunikation benützt wird.
Man wird daher auch immer die gleiche Komponenten-Klasse für die ser. Schnittstelle benützen.

Also könnte man doch folgende gemeinsame Basisklasse erstellen:
Delphi-Quellcode:
TAlarmAnlage = class
public
  property ComPort : TComPort read FComPort write FComPort; // *)
end;

TMB100 = class(TAlarmAnlage)
...
end;
Das Property bei *) ist nun so zu verstehen:
Ich, die Klasse TAlarmAnlage, erwarte von dem Programmierer, dass ich ein Objekt vom Typ TComPort übergeben bekomme; ansonsten kann ich nicht arbeiten.
Ausserdem erwarte ich, dass der Zustand der seriellen Schnittstelle gleich "Connected" ist.
Baudrate, Parity,usw. sind mir piepegal; gib' mir einfach eine aktive Komponente über die ich kommunizieren kann!

Jemand, der mit der Klasse TMB100 arbeiten will muss also zuerst ein Objekt der Klasse TComPort erzeugen, dann Baudrate, Parity,usw. einstellen und die Schnittstelle öffnen.

Die Klasse TAlarmAnlage machst es sich selbst ganz einfach und überträgt die Aufgabe der Initialisierung des Kommunikationsobjekts an denjenigen, der die Klasse benützen will.
Was für Dich im ersten Augenblick vielleicht wie Faulheit aussieht ist Softwaretechnisch aber ein grosser Vorteil.
Die Klasse könnte genausogut eine geöffnete TCP/IP-Verbindung verlangen und darüber kommunizieren ohne sich das Geringste um die IP-Adresse zu scheren.
TAlarmAnlage braucht einfach nur ein Kommunikations-Objekt über das sie lesen und schreiben kann.

Jens Hartmann 30. Dez 2010 23:24

AW: Aufbau eigene Klasse mit Property und TStrings
 
Das Problem ist etwas größer, zumindest glaube ich das, als wie es aussieht. Also ich versuche es mal zu
beschreiben.
Ich habe 10 verschiedene Hardwarekomponenten (Aktuell, es können mehr werden).
Zusätzlich möchte ich wenigstens, gleichzeitige 4 Verbindung über die Serielle Schnittstellen ermöglichen. Soll
bedeuten, der entsprechende PC stellt die Schnittstellen COM1-COM4. Oder COM1, COM3, COM4, COM5 wie auch immer.

Daraus folgt doch:

Ich nehme ein Datenmodule, füge diesem 4 ComPort Komponenten zu, welche über den Dienst oder über die Anwendung
konfiguriert und genutzt werden können.

Das Datenmodule speichert die Empfangenen Daten in der Datenbank und liefert der Anwendung die visuelle Anzeige.
Der Dienst benötigt dies ja nicht. Deshalb möchte ich dieses auch über Propertys lösen, da ich diese ja aus dem
Dienst einfach nicht abfragen brauche. Die Anwendung nutzt diese Propertys.

Also, 1 Anwendung oder 1 Dienst

nutzen 1 Datenmodul mit der Anbindung an die entsprechende Datenbank.

Dann kann das Datenmodul mit der entsprechenden Klasse kommunizieren. Soll heißen, da jede Hardware andere
Einstellungen nutzt(Baudrate, Parität, Parser), verankere ich die Einstellungen doch am besten in der
entsprechenden Klasse. So habe ich doch den Vorteil, einen neue Hardware, eine neue Klasse. An dem Datenmodul
muss ich nichts mehr ändern. Das Datenmodul holt sich einfach aus der entsprechenden Klasse die Konfiguration
der Schnittstelle.
Desweiteren wird ja der Parser der entsprechenden Hardware in der Klasse implementiert. Somit habe ich alles
was zur Hardware MB100 gehört in der Klasse MB100, usw.
Das, so denke ich, würde zur Folge haben, das die Anwendung irgendwann fertig ist und nur noch der
BUG-Beseitigung Aufmerksamkeit gespendet werden muss. Sollte eine neue Hardware notwendig sein, wird eine neue
Klasse geschrieben, welche erstmal kopiert und nur noch angepasst werden muss. Die Klasse kann ins Datenmodul
implementiert werden fertig.

Naja, vieleicht sind das ja Traumvorstellungen. Aber so ist zur Zeit mein Überlegung.

Vieleicht aber zeigt Ihr mir wo die Gedanken Fehler liegen,

Gruß Jens

PS: Da glaubt man, man hat viel gelesen und es verstanden, aber irgendwie ist man immer noch so dumm wie vorher.

Quellen:
Kurzreferenz
Wikibooks

und viele mehr....

Bummi 30. Dez 2010 23:31

AW: Aufbau eigene Klasse mit Property und TStrings
 
@Jens

Ich habe nach dem von Dir beschriebenen Schema bereits Steuerungen/Messungen umgesetzt, das funktioniert. Eine Basisklasse mit allgemeinen Eigenschaften und Ereignisbehandlungsroutinen, Fehlerroutinen (z.T. als virtual und oder abstract) und darauf die spezifischen Klassen für das Parsen/Interpretieren/Schreiben.

sx2008 31. Dez 2010 01:12

AW: Aufbau eigene Klasse mit Property und TStrings
 
Die Frage ist doch: WER erzeugt die Objekte für die seriellen Schnittstellen?
Nach deinem Konzept nach hältst du 4 ComPort-Objekte auf dem Datenmodul bereit.
Ich würde diese ComPort-Objekte aber dynamisch nach Bedarf über eine Factory-Klasse erzeugen.
Diese Factory-Klasse benötigt für ihre Arbeit den Dateinamen einer Ini-Datei.
Die Ini-Datei sieht z.B. so aus:
Code:
[Config]
DeviceCount=2
[Device1]
Device=MB100
Port=COM1
Baudrate=9600
Parity=N
[Device2]
Device=MB256
Port=COM3
Baudrate=19200
Parity=N
Hier der Grobentwurf der Factory-Klasse:
Delphi-Quellcode:
TDeviceFactory=class

public
  class function CreateMB100:TAlarmAnlage;
  class function CreateMB256:TAlarmAnlage;
  ...
 
  function GetDeviceCount:integer; // Anzahl der Geräte in der Ini-Datei

  function CreateDevice(deviceNr:integer):TAlarmAnlage;
  property IniFilename:string;
end;

function TDeviceFactory.CreateDevice(deviceNr:integer):TAlarmAnlage;
var
  devicename : string;
  inifile : TIniFile;
  section : string;
  comport : TComPort;
begin
  section := Format('Device%d', [devicenr]);
  inifile := TIniFile.Create(IniFilename);
  devicename := inifile.ReadString(section, 'Device');

  // hier ggf. prüfen, ob es die Section überhaupt gibt
 
  // Hardwarekomponente erzeugen
  if devicename='MB100' then
    Result := CreateMB100
  else if devicename='MB256' then
   Result := CreateMB256
  else if ...
  else
    raise Exception.CreateFmt('unbekanntes Device <%s>', [devicename]);
 
  // jetzt wird noch das Kommunikations-Objekt benötigt
  comport := TComPort.Create(Application {siehe Text unten});
  comport.Port := inifile.ReadString(section, 'Port');
  comport.BaudRate := inifile.ReadInteger(section, 'Baudrate');
  comport.Parity := inifile.ReadString(section, 'Parity');
  // hier ggf. weitere Properties (Handshake, Datenbits) berücksichtigen
 
  // Geräte-Objekt und Kommunikations-Objekt verheiraten
  Result.Comport := comport;
  comport.OnReceive := Result.ReceiveHandler; // Eventhandler zuweisen
 
  // serielle Schnittstelle öffnen
  comport.Connected := True;
  inifile.Free;
end;
In der obigen Funktion fehlen noch einige Feinheiten sowie eine wasserdichte Fehlerbehandlung.
Aber das Prinzip sollte klar sein.
Die Hardwarekomponente (bzw. Geräteklasse) kümmert sich nicht um Baudrate usw.
Die Factory erzeugt anhand den Informationen in der Ini-Datei die Hardwarekomponente sowie die Komponente für die serielle Schnittstelle und baut diese zu einem funktionierenden Verbund zusammen.

Es bleibt noch die Frage offen: WER ist der Owner der ComPort-Komponente?
Im Code oben habe ich Application angegeben; das ist noch etwas unschön.
Falls die Hardwarekomponente tatsächlich eine echte Komponente ist, würde man schreiben:
Delphi-Quellcode:
// jetzt wird noch das Kommunikations-Objekt benötigt
comport := TComPort.Create(Result {=die neu erzeugte Hardwarekomponente});
comport.Port := inifile.ReadString(section, 'Port');
Lass das mal etwas absacken; das Konzept der Factory-Klasse mag zunächst etwas fremd aussehen, hat aber entscheidende Vorteile.

Jens Hartmann 31. Dez 2010 09:04

AW: Aufbau eigene Klasse mit Property und TStrings
 
Zitat:

Zitat von Bummi
Ich habe nach dem von Dir beschriebenen Schema bereits Steuerungen/Messungen umgesetzt, das funktioniert. Eine Basisklasse mit allgemeinen Eigenschaften und Ereignisbehandlungsroutinen, Fehlerroutinen (z.T. als virtual und oder abstract) und darauf die spezifischen Klassen für das Parsen/Interpretieren/Schreiben.

So dachte ich halt und so wollte ich es eigendlich umsetzten...

Zitat:

Zitat von sx2008
Die Frage ist doch: WER erzeugt die Objekte für die seriellen Schnittstellen?
Nach deinem Konzept nach hältst du 4 ComPort-Objekte auf dem Datenmodul bereit.
Ich würde diese ComPort-Objekte aber dynamisch nach Bedarf über eine Factory-Klasse erzeugen.
Diese Factory-Klasse benötigt für ihre Arbeit den Dateinamen einer Ini-Datei.

Die Komponente stammt aus dem CPortLib Package. Dies hat in meinem Fall den Vorteil, das Sie über eine zusätzlich Komponente ComDataPacket verfügt. Darüber realisiere ich, das ich immer nur einen Datensatz empfange. Und somit das Parsen schon auf die Abarbeitung von einem Datensatz beschränken kann. Sicherlich könnt ich diese Komponente auch dynamisch erzeugen, fragt sich nur, ob das bei vier bzw. acht Komponenten wirklich notwendig ist.

Zitat:

Zitat von sx2008
In der obigen Funktion fehlen noch einige Feinheiten sowie eine wasserdichte Fehlerbehandlung.
Aber das Prinzip sollte klar sein.

Das Prinzip ist klar, und verstehe ich auch. Der Gedankengang scheint mir für meine Zwecke halt nur zu hoch angesetzt. Wenn ich nochmal auf meine Idee gehe, dann würde ich doch eines zumindestens Übersichtlich lösen. Alles was zur Zentrale A gehört, befindet sich auch in dessen Klasse. Über eine Ini-Datei oder ähnliches brauche ich nur noch festzulegen, welchen Zentrale welchen PORT nutzen tut. Eine Anbindung anderer Art als seriell wird nicht notwendig sein, daher dachte ich das alles schön sauber in eine Klasse zu vereinen.

Zitat:

Zitat von sx2008
Delphi-Quellcode:
Delphi-Quellcode:
// jetzt wird noch das Kommunikations-Objekt benötigt
comport := TComPort.Create(Result {=die neu erzeugte Hardwarekomponente});
comport.Port := inifile.ReadString(section, 'Port');
Lass das mal etwas absacken; das Konzept der Factory-Klasse mag zunächst etwas fremd aussehen, hat aber entscheidende Vorteile.

Andererseits, kann ich die Idee von dir auch nachvollziehen und ich überlege, ob ich es so mal testen soll. Allerdings fehlen mir da noch so ein Paar zusammenhänge. Ich muss wie du es schon geschrieben hast, das ganze erstmal sacken lassen und mal die Google-Welt strapazieren.

Danke aber schon mal für die vielen Inputs.

Gruß Jens

r2c2 31. Dez 2010 14:35

AW: Aufbau eigene Klasse mit Property und TStrings
 
Bin gestern mit dem Blog-Artikel über Magic Values doch nicht mehr fertig geworden. Jetzt aber endlich: http://www.christian-rehn.de/2010/12/31/magic-values/

Zitat:

Ich nehme ein Datenmodule, füge diesem 4 ComPort Komponenten zu, welche über den Dienst oder über die Anwendung
konfiguriert und genutzt werden können.
Wie viele Programme hast du jetzt? Ich dachte zwei: Einen Dienst und eine GUI. Und die GUI muss ja vom Com-Zeug eigentlich nix wissen. Oder hab ich da was falsch verstanden?

Zitat:

Soll heißen, da jede Hardware andere
Einstellungen nutzt(Baudrate, Parität, Parser), verankere ich die Einstellungen doch am besten in der
entsprechenden Klasse.
Nein, das sind Parameter, die man aus ner Konfigurationsdatei lesen sollte. "Put Abstractions in Code, details in metadata". Der Einfachheit halber kann man dieses Prinzip auch mal ignorieren (KISS). Das is mal wieder so ne Trade-off-Geschichte...

In diesem Fall würde ich aber auch die Factory nehmen. Egal ob jetzt von mir aus in der Factory hardgecoded oder aus ner Konfigurationsdatei gelesen. Aber deine Klasse muss von Baudrate & Co. eigentlich erstmal nix wissen. Das ist Sache von TComPort. ==> Separation of Concerns.

Zitat:

So habe ich doch den Vorteil, einen neue Hardware, eine neue Klasse.
Das hast du bei DependencyInjection genauso.

Zitat:

Desweiteren wird ja der Parser der entsprechenden Hardware in der Klasse implementiert. Somit habe ich alles
was zur Hardware MB100 gehört in der Klasse MB100, usw.
Das hört sich für mich an, als wären deine Klassen zu groß. Als täten sie zu viel. Eine Klasse sollte genau eine Aufgabe haben. Nicht mehr und nicht weniger. ==> Single Responsibility Princliple.

Ich weiß nicht, was deine Klasse alles tun soll, aber, wenn sie mehr tut als das Protokoll verarbeiten (also vermutlich das, was du Parsen nennst), dann solltest du das in mehrere Klassen aufteilen.

Zitat:

Alles was zur Zentrale A gehört, befindet sich auch in dessen Klasse.
Eben das genau nicht. Das ist nicht objektorientiert. Nicht alles in eine Klasse. Sonst kriegst du Gottklassen, die letztendlich nur noch unübersichtlich sind. D.h. du solltest feiner Aufteilen. Es ist schon richtig, dass du das, was zusammengehört auch zusammen haben sollst, aber das muss nicht notwenidigerweise eine einzige Klasse sin. Das kann auch eine Gruppe von Klassen sein, sofern jede Klasse für sich genommen eine abgegrenzte Aufgabe hat und du dich nicht in Redundanzen verrennst. Lies mal http://www.christian-rehn.de/2009/08...hinen-und-ood/ bzw. http://www.objectmentor.com/resource...offeeMaker.pdf

mfg

Christian

Bummi 31. Dez 2010 15:10

AW: Aufbau eigene Klasse mit Property und TStrings
 
Man kann und ich würde es in diesem Fall so tun, das ganze auch als hochspezialisierte/erweiterte Comportklassen sehen.

Jens Hartmann 3. Jan 2011 18:25

AW: Aufbau eigene Klasse mit Property und TStrings
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von r2c2
Bin gestern mit dem Blog-Artikel über Magic Values doch nicht mehr fertig geworden. Jetzt aber endlich: http://www.christian-rehn.de/2010/12/31/magic-values/

Danke für den Hinweis, werde ich mir aufjedenfall mal ansehen.

Zitat:

Zitat von r2c2
Wie viele Programme hast du jetzt? Ich dachte zwei: Einen Dienst und eine GUI. Und die GUI muss ja vom Com-Zeug eigentlich nix wissen. Oder hab ich da was falsch verstanden?

Ja, ich habe vor zwei Programme zu schreiben. Allerdings sollen beide die selbe Aufgabe erfüllen. Soll heißen, startet man die Anwendung, hab ich eine Oberfläche auf der ich die Aktivitäten auf den Com-Schnittstellen und den Datenverkehr der Datenbank beobachten kann. Siehe Screenshot 1.Desweiteren möchte ich einen Dienst, der die selbe Aufgabe erfüllt, allerdings die Funktion eines Dienstes hat. z.B. "Keine Windows Anmeldung erforderlich". Somit muss aber die GUI auch einen Bezug zu den COM-Ports haben. Zum Außerwerden der Datenbank, so vermute ich war dein Gedanke, gibt es eine weitere Anwendung die WEB-basierent läuft.

Zitat:

Zitat von r2c2
Nein, das sind Parameter, die man aus ner Konfigurationsdatei lesen sollte. "Put Abstractions in Code, details in metadata". Der Einfachheit halber kann man dieses Prinzip auch mal ignorieren (KISS). Das is mal wieder so ne Trade-off-Geschichte...

In diesem Fall würde ich aber auch die Factory nehmen. Egal ob jetzt von mir aus in der Factory hardgecoded oder aus ner Konfigurationsdatei gelesen. Aber deine Klasse muss von Baudrate & Co. eigentlich erstmal nix wissen. Das ist Sache von TComPort. ==> Separation of Concerns.

Hab ich verstanden, werde ich allerdings für mich als Zusammenhang in die Unit der entsprechenden Hardware packen. Da denke ich so wie "Bummi".

Zitat:

Zitat von Bummi
Man kann und ich würde es in diesem Fall so tun, das ganze auch als hochspezialisierte/erweiterte Comportklassen sehen.

Zitat:

Zitat von r2c2
Das hört sich für mich an, als wären deine Klassen zu groß. Als täten sie zu viel. Eine Klasse sollte genau eine Aufgabe haben. Nicht mehr und nicht weniger. ==> Single Responsibility Princliple.

Ich weiß nicht, was deine Klasse alles tun soll, aber, wenn sie mehr tut als das Protokoll verarbeiten (also vermutlich das, was du Parsen nennst), dann solltest du das in mehrere Klassen aufteilen.

Von der Sache her ja, allerdings macht diese Klasse/das Unit ja nur folgendes. Die Parameter bereitstellen welche notwendig sind um Daten anzufordern und zweitens diese Angeforderten Daten Parsen"Verarbeiten" bzw. in seine Einzelteile zerlegen und an das Datenmodul zurück zu geben. Die an das Datenmodul übergebenen Daten werden dann von dem Datenmodul in die Datenbank geschrieben. Somit wäre in der Klasse bzw. Unit ein kleines Packet max. 4 Methoden die aber für eine Aufgabe zuständig sind. Alle diese Methoden werden benötigt um Daten von A - B zu bekommen. Und daher denke ich, das die Klasse nicht zu viele Aufgaben hat. Vieleicht sollte ich einfach sagen, das ich damit meinen Code mehr Strukturieren will. Hätte ich dies nicht vor, würde diese Aufgabe auch im Datenmodul Platz finden.

Vieleicht konnte ich meine Ziel jetzt noch etwas besser erläutern. Was mir wichtig ist, ist halt, das wenn schon eine externe Unit, das diese auch sauber Programmiert ist. Ich fange gerade halt erst mit Klassen an und da will ich halt nicht direkt den größten Bock schießen.

Gruß Jens

bernau 3. Jan 2011 23:16

AW: Aufbau eigene Klasse mit Property und TStrings
 
Zitat:

Zitat von himitsu (Beitrag 1071187)

Delphi-Quellcode:
property ComPortProperties: TStrings read FComPortProperty;
.

wenn du unbedingt write haben willst, dann implementier dieses als 'ne Assign-Methode,

Ist zwar eigendlich schon geklärt, daß man für die Lösung nicht unbedingt Tstrings benötigt, sondern ein Record verwenden kann. Aber ich möchte mal grundsätzlich auf das Thema TStrings als Property eingehen. Deinen Post, himitsu, habe ich mal als Aufhänger genommen. Könnte aber auch 2-3 anderes Posts Quoten, die ein Assign im Setter vorschlagen.

Im Zweifel weis man nicht, wie die Setter-Methode implementiert ist. Diese könnte eine Zuweisung einer Stringliste auf eine interne Variable sein (Interne Variable wurde dann z.B. nicht im Constructor instanziert), oder eben ein Assign auf eine bereits instanzierte Variable.

Daher gibt es bei mir die Konvention, daß eine interne bereits im Create instanzierte Stringliste nur über eine Function oder ein Readonly-Property zugreifbar ist.

Delphi-Quellcode:
property ComPortPropertys: TStrings read FComPortPropertys;
Delphi-Quellcode:
function ComPortPropertys: TStrings;
begin
 result:=FComPortPropertys;
end;
Eine Stringliste, die im Objekt nicht instanziert wurde, die bekommt eine Getter UND eine Setter-Methode. Mit dieser Vorgabe weis ich genau, ob das Objekt die Zerstörung der übergebenen Stringlist übernimmt. Soll die Zerstörung der zugewiesenen Stringliste nicht vom Objekt übernommen werden, dann muss vorher ein NIL zugewiesen werden, oder es gibt ein Property mit dem namen OwnsStringlist. Gilt im übrigen nicht nur für Stringlisten, sonder für alle Objekte, die in einem Objekt zugreifbar sind.


Alles andere birgt die Gefahr eines Speicherlecks oder einer Schutzverletzung.


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