Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi kleines OOP Beispiel bitte um Anmerk./Verbesserungvorschläge (https://www.delphipraxis.net/122342-kleines-oop-beispiel-bitte-um-anmerk-verbesserungvorschlaege.html)

newbe 14. Okt 2008 15:00


kleines OOP Beispiel bitte um Anmerk./Verbesserungvorschläge
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Delphipraxis Community,

Ich bin ein Delphi-Neuling und habe mal ein kleines ganz einfaches Übungsproject erstellt. Ich würde mich freuen, wenn hier mal ein paar erfahrenere
Leute mal auf Fehler, oder Verbesserungmöglichkeiten prüfen könnten. Mir geht es vorallem um das Prinzip der Datenkapselung. Eventuell könnt ihr mir aber auch noch andere Hinweise zum Codedesign geben. Wie würdet ihr diees Beispiel elegant umsetzten? Für alle Hinweise bin ich dankbar.

Was mich speziell noch Interessieren würde.

Wozu benötigt man den Constructor/Destructor?
Benötigt man ihn im diesem Beispiel überhaupt?
Und wozu dient er sonst im allgemeinen?
Was ist der Unterschied zwischen Constructor und einer Klasse.init; procedure?
Was bedeutet der "Default 0" Wert in der Property Deklaration?

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;


  TMeineDaten = class(TObject)
    private
      fWert1 : Integer;
      fWert2 : Integer;
      fErgebnis : Integer;
    procedure SetWert1(const Value: Integer);
    procedure SetWert2(const Value: Integer);
    procedure BerechneErgebnis();
    procedure SetErgebniss(const Value: Integer);

    public
      constructor create;
      destructor destroy;
      property Wert1 : Integer read FWert1 write SetWert1 Default 0;
      property Wert2 : Integer read FWert2 write SetWert2 Default 0;
      property Ergebnis : Integer read FErgebnis write SetErgebniss Default 0;
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}



{ TMeineDaten }

procedure TMeineDaten.BerechneErgebnis();
begin
SetErgebniss(fWert1+fWert2);
end;

constructor TMeineDaten.create;
begin

end;

destructor TMeineDaten.destroy;
begin

end;

procedure TMeineDaten.SetErgebniss(const Value: Integer);
begin
  FErgebnis := Value;
end;

procedure TMeineDaten.SetWert1(const Value: Integer);
begin
  FWert1 := Value;
end;

procedure TMeineDaten.SetWert2(const Value: Integer);
begin
  FWert2 := Value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var test: TMeineDaten;
begin
  test:=TMeineDaten.create;
  test.SetWert1(strtoint(edit1.text));
  test.SetWert2(strtoint(edit2.text));
  test.BerechneErgebnis();
  label1.caption:=inttostr(test.ergebnis);
  test.Free;
end;

end.

DeddyH 14. Okt 2008 15:15

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Hallo und Willkommen in der DP :dp:

bis auf den leeren Konstruktor/Destruktor und fehlende Ressourcen-Schutzblöcke (try-finally) habe ich nichts Auffälliges bemerkt. Destroy ist als virtuell deklariert und sollte daher mit override überschrieben werden. Und zumindest ein inherited sollte dann schon drinstehen. Ansonsten OK.

angos 14. Okt 2008 15:30

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
hi,

Zitat:

Wozu benötigt man den Constructor/Destructor?
Einen Constructor bzw Destructor benötigst du, um dein Objekt zu initialisieren. Du kannst dort Werte festlegen, jedesmal beim erzeugen eines solchen Objektes durchzuführende FUnktionen aufrufen, etc..

Zitat:

Benötigt man ihn im diesem Beispiel überhaupt?
Nein, Constructor und Destructor sind ja schon in TOBject hinterlegt, von welchem du ja erbst. Da du keine weiteren Einstellungen machen möchtest (scheinbar), kannst du dir diese Deklarationen auch sparen.

Zitat:

Und wozu dient er sonst im allgemeinen?
Steht oben ;)

Zitat:

Was ist der Unterschied zwischen Constructor und einer Klasse.init; procedure?
hmm, ich würd jetzt mal behaupten, der Constructor ist das was ich oben beschrieben habe und Init; ist einfach nut eine procedur, welche nach dem Erzeugen des Objekt manuell aufgerufen werden muss..

Zitat:

Was bedeutet der "Default 0" Wert in der Property Deklaration?
hmm, kann ich dir leider gerade nicht sagen...würd mich auch mal interessieren :)

Gruß
Ansgar

jfheins 14. Okt 2008 15:32

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Zitat:

Zitat von angos
Zitat:

Was bedeutet der "Default 0" Wert in der Property Deklaration?
hmm, kann ich dir leider gerade nicht sagen...würd mich auch mal interessieren :)

Gruß
Ansgar

Bedeutet, dass bei einer published Property dieser Wert nicht in der dfm gespeichert wird.

d.h. wenn du im Consttructor einen default-Wert setzt, kannst du das so kenntlich machen, damit nicht unnötige info in der dfm gespeichert wird.

Evtl. dient es auch dazu, im Objekt-Inspector einen nicht-default-Wert fett zu markieren.

DeddyH 14. Okt 2008 15:36

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Zitat:

Die optionalen Direktiven stored, default und nodefault sind Speicherangaben. Sie haben keinerlei Auswirkungen auf die Funktionsweise des Programms, sondern steuern lediglich die )Verwaltung der Laufzeit-Typinformationen. Genauer gesagt bestimmen sie, ob die Werte der published-Eigenschaften in der Formulardatei (DFM) gespeichert werden.
Nach der Angabe stored muß der Wert True oder False, der Name eines Booleschen Feldes oder der Name einer parameterlosen Methode folgen, die einen Booleschen Wert zurückgibt. Ein Beispiel:

Delphi-Quellcode:
property Name: TComponentName read FName write SetName stored False;
Wird eine Eigenschaft ohne die Angabe stored deklariert, entspricht dies der Definition stored True.
Nach default muß eine Konstante angegeben werden, die denselben Datentyp wie die Eigenschaft hat:

Delphi-Quellcode:
property Tag: Longint read FTag write FTag default 0;
Mit Hilfe des Bezeichners nodefault kann ein geerbter default-Wert ohne Angabe eines neues Wertes außer Kraft gesetzt werden. default und nodefault werden nur für Ordinal- und Mengentypen unterstützt, bei denen die Ober- und Untergrenze des Basistyps einen Ordinalwert zwischen 0 und 31 hat. Wenn eine solche Eigenschaft ohne default oder nodefault deklariert wird, gilt sie als mit nodefault deklariert. Reelle Zahlen, Zeiger und Strings besitzen den impliziten default-Wert von 0, nil bzw. '' (einen leeren String).

Beim Speichern einer Komponente werden die Speicherbezeichner ihrer published-Eigenschaften überprüft. Wenn sich der aktuelle Wert einer Eigenschaft von ihrem default
-Wert unterscheidet (oder kein default-Wert vorhanden ist) und stored True ist, wird der Wert gespeichert. Treffen diese Bedingungen nicht zu, wird der Wert nicht gespeichert.

Hinweis: Bei Array-Eigenschaften werden Speicherangaben nicht unterstützt. default hat bei diesem Eigenschaftstyp eine andere Bedeutung (siehe Array-Eigenschaften).

Medium 14. Okt 2008 15:39

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Zitat:

Zitat von newbe
1. Wozu benötigt man den Constructor/Destructor?
2. Benötigt man ihn im diesem Beispiel überhaupt?
3. Und wozu dient er sonst im allgemeinen?
4. Was ist der Unterschied zwischen Constructor und einer Klasse.init; procedure?
5. Was bedeutet der "Default 0" Wert in der Property Deklaration?

1. Der Konstruktor ist eine vom Compiler "unsichtbar" erweiterte Methode, bei der als aller erstes der Speicher vom System reserviert wird, den die Instanz benötigt. Sein Rückgabewert ist immer ein Zeiger auf den Anfang dieses Bereiches. Der Destruktor macht das Gegenteil: Er gibt den vom Konstruktor angeforderten Speicher zur Neuverteilung in die Obhut des Betriebssystems zurück.

2. Prinzipiell benötigt man beide immer, weswegen der Urahne aller Klassen (TObject) beide bereits enthält. Du musst sie aber nicht wie in deinem Beispiel leer überschreiben, wenn du nicht selbst noch Werte/Felder in deiner Klasse (de)initialisieren willst/brauchst. Dort kann man sie getrost weg lassen, wodurch automatisch der Konstruktor/Destruktor der Elternklasse verwendet wird.

3. s.o. :)

4. Eine init Prozedur ist eine einfache Methode. Sie würde keinen Speicher anfordern, d.h. sie kann erst nach dem Konstruktor aufgerufen werden - wie jede andere Methode auch (ausgenommen Klassenmethoden). Die Namensgebung "init" ist dabei völlig egal. Es ist zudem im generellen eigentlich üblich Initialisierungen im Konstruktor vorzunehmen, und dafür nicht noch eine separat aufzurufende Methode anzulegen (es sei denn der Konstruktor ruft diese dann auf).

5. Damit legst du den Wert der Eigenschaft fest, den sie direkt nach Aufruf des Konstruktors hat. Unter Delphi werden Felder grundsätzlich mit 0 initialisiert, weshalb hier die explizite Angabe nicht nötig wäre. Sie tut aber auch nicht weh :). Default-Werte lassen sich übrigens nur für einfache Datentypen vorgeben, d.h. nicht für Klassen, Records und Arrays (da bin ich mir zumindest 99%ig sicher).


Edit: Yay, sind dem roten Kasten satte 3 Beiträge durchgegangen :shock:

sirius 14. Okt 2008 15:46

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Wenn du einen Constrcutor mit xyz:=TKlasse.create (create ist jetzt der constructor, was ja normalerweise in Delphi auch so ist) aufrufst, führt der Compiler zu Beginn (und auch am Ende) des Constructors noch etwas Code ein. Dieser hat die Aufgabe den Speicherplatz zu reservieren, der für alle deine Felder (Variablen in der Klasse) benötigt werden. Einen Zeiger auf den Speicherplatz bekommst du ja dann vom constructor zurück und Speicherst ihn in xyz.
Der Constructor verbindet quasi zwei Aufgaben:
  1. Den Speicherplatz zu reservieren
  2. Felder zu initialisieren, wenn nötig (macht procedure init überflüssig)
Am Ende des Constructors wird dann noch die Methode AfterConstruction aufgerufen.

Du kannst einen Constrcutor auch ganz normal als Methode aufrufen, über xyz.create. Dann wird ausschließlich der Code innerhalb des Constructors ausgeführt. Dann sollte aber xyz schon vorher initialisiert sein. Genauso ist es, wenn du einen Vorfahr-constructor mit inherited aufrufst.

Der Destructor löscht dann übrigens die gesamten Felder wieder aus dem Speicher. Der macht das übrigens immer. Den kann man auch nicht als normale Methode verwenden.



default ist eine Angabe, welche sich für published properties anbietet. Defaultwerte müssen nicht in der Ressource gespeichert.

mjustin 14. Okt 2008 15:48

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Zitat:

Zitat von newbe
Delphi-Quellcode:
private
...
procedure BerechneErgebnis();

Wenn diese Prozedur private deklariert ist, ist ihre Sichtbarkeit eventuell etwas zu sehr begrenzt um mit der Klasse sinnvoll zu arbeiten...

DeddyH 14. Okt 2008 15:57

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Stimmt, fällt im Beispiel nur nicht auf, weil es in derselben Unit steht.

newbe 14. Okt 2008 18:50

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
@All erstmal Danke für eure Antworten. :)

@mjustin & DeddyH

Ich habe die Procedur Berechne Ergebnis absichtlich als Privat Deklariert um die Sichtbarkeit zu limitieren und das
OOP-Prinzip der Datenkapselung umzusetzen. Ich könnte die Procedure natürlich auch als Public deklarieren um beispielsweise aus einer anderen Unit darauf zugreifen zu können. Mir fällt nur kein Grund ein warum dies nötig sein sollte. Vielleicht kannst du
da mal ein sinnvolles Beispiel bringen das mir zeigt für welche Anforderungen diese Umsetzung angebracht ist.

Grundsätzlich hätte ich am liebsten intelligente Objecte. In diesem Fall strebe ich eine strickte Trennung von externen
Usereingaben und internen Berechnungen an. Sprich ich würde auf die Procedure Berechne Ergebnis am liebsten komplett
verzichten. Ich hätte es gern so, das wenn beide Werte eingegeben sind das Object selbständig addiert und das Ergebnis in
der Property Ergebnis zu Verfügung stellt. Aber vielleicht habe ich das auch falsche Vorstelllungen und sowas ist mit
OOP gar nicht machbar/Sinnvoll! Ich stelle mir das so vor. Achtung ungetestet.

mfG newbe

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen } 
  public
    { Public-Deklarationen } 
  end;


  TMeineDaten = class(TObject)
    private
      fWert1 : Integer;
      fWert2 : Integer;
      fErgebnis : Integer;
    procedure SetWert1(const Value: Integer);
    procedure SetWert2(const Value: Integer);
    function GetErgebniss() Integer; //Getter eingefügt
    procedure SetErgebniss(const Value: Integer);

    public
      constructor create;
      destructor destroy;
      property Wert1 : Integer read FWert1 write SetWert1 Default 0;
      property Wert2 : Integer read FWert2 write SetWert2 Default 0;
      property Ergebnis : Integer read GetErgebnis write SetErgebniss Default 0; //Getter eingefügt
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm} 

ungetestet!

{ TMeineDaten } 


function GetErgebniss() :Integer;
begin
  SetErgebnis(fWert1+fWert2); //oder Setter weglassen und direkt fErgebnis:=fWert1+fWert2;
end;


procedure TMeineDaten.SetErgebniss(const Value: Integer);
begin
  FErgebnis := Value;
end;

procedure TMeineDaten.SetWert1(const Value: Integer);
begin
  FWert1 := Value; //Wertebereich checken
end;

procedure TMeineDaten.SetWert2(const Value: Integer);
begin
  FWert2 := Value; //Wertebereich checken
end;

procedure TForm1.Button1Click(Sender: TObject);
var test: TMeineDaten;
begin
  test:=TMeineDaten.create;
  test.SetWert1(strtoint(edit1.text));
  test.SetWert2(strtoint(edit2.text));
 
// weg damit -> test.BerechneErgebnis();

  label1.caption:=inttostr(test.ergebnis);
  test.Free;
end;

end.

sirius 14. Okt 2008 18:58

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Dann lass doch den Setter bei Ergebnis weg und lass auch gleich fErgebnis weg und gebe bei GetErgebnis einfach die summe zurück.

DeddyH 14. Okt 2008 18:58

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Du darfst dann auch nicht die Setter-Methoden direkt aufrufen, sondern müsstest die Properties setzen, welche dann wiederum in ihrem jeweiligen Setter die Berechnung durchführen können. Das kann auch durchaus sinnvoll sein, ich sehe da jetzt keinen Widerspruch zur OOP.

[edit]
Zusammengefasst könnte das dann so aussehen:
Delphi-Quellcode:
  TMeineDaten = class(TObject)
    private
      fWert1 : Integer;
      fWert2 : Integer;
      procedure SetWert1(const Value: Integer);
      procedure SetWert2(const Value: Integer);
      function GetErgebniss(): Integer;
    public
      property Wert1 : Integer read FWert1 write SetWert1 Default 0;
      property Wert2 : Integer read FWert2 write SetWert2 Default 0;
      property Ergebnis : Integer read GetErgebnis;
  end;

procedure TMeineDaten.SetWert1(const Value: Integer);
begin
  FWert1 := Value;  
end;

procedure TMeineDaten.SetWert2(const Value: Integer);
begin
  FWert2 := Value;
end;

function TMeineDaten.GetErgebniss(): Integer;
begin
  Result := fWert1 + fWert2;
end;
[/edit]

jfheins 14. Okt 2008 19:55

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Ich würde allerdings ie Getter uns Setter protected machen, um jemendem, der die Klasse ableiten möchte, das Leben nicht unnötig kompliziert zu machen ;)

Ist aber nur eine klitzekleine Anregung, ich will ja keinen überfordern :stupid:

newbe 14. Okt 2008 20:25

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
@DeddyH

bin begeistert von deinem Code. :)

Also das sieht doch echt schlank und elegant aus. Ich würde nur gern das Ergebnis im Object storen, da er ja sonst bei jedem
zugriff auf die Property diese Berechnung ausführt. Und das kann bei Komplexen sachen schon mächtig performance kosten.

Also sprich die Funktion GetErgebniss soll intern Checken ob die Berechnung schon ausgeführt wurde, und das Ergebnis dann
aus der Property fErgebnis holen, anstatt bei jedem zugriff nochmal zu berechnen.

Aber ansonsten finde ich deinen Code top. :Thumbs up:

mfG newbe

sirius 14. Okt 2008 20:33

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Delphi-Quellcode:
TMeineDaten = class(TObject)
    private
      fWert1 : Integer;
      fWert2 : Integer;
      FErgebnis: Integer;
      FBerechnet: Boolean;
      procedure SetWert1(const Value: Integer);
      procedure SetWert2(const Value: Integer);
      function GetErgebniss(): Integer;
    public
      property Wert1 : Integer read FWert1 write SetWert1 Default 0;
      property Wert2 : Integer read FWert2 write SetWert2 Default 0;
      property Ergebnis : Integer read GetErgebnis;
  end;

procedure TMeineDaten.SetWert1(const Value: Integer);
begin
  FWert1 := Value;  
  FBerechnet:=false;
end;

procedure TMeineDaten.SetWert2(const Value: Integer);
begin
  FWert2 := Value;
  FBerechnet:=False;
end;

function TMeineDaten.GetErgebniss(): Integer;
begin
  if not FBerechnet then
  begin
    FErgebnis:= fWert1 + fWert2;
    FBerechnet:=true;
  end;
  result:= FErgebnis;
end;
Ist mir zu lang, dass jetzt zu erklären.
FBerechnet könnte man noch im constrcutor initialisieren.

newbe 15. Okt 2008 04:52

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
@sirius

Genau so habe ich mir das vorgestellt. :)

Eine letzte Frage hätte ich noch zu dem Themenkomplex. Unzwar zum constructor. Du weist darauf hin, das es wichtig wäre
den Boolean zu initialisieren, was ja auch richtig ist. Jedoch verstehe ich eines nicht. Im Inet habe ich irgendwo aufgeschnappt,
das man Klassenvariablen bzw. Felder immer erst nach abgeschlossener Object-Erstellung initialisieren sollte. Ich habe das so verstanden, das ich erst das Object erstellen soll und erst dann eine extra Init Prozedure aufrufen soll um Seiteneffekte zu vermeiden. Also in dieser Art.

Delphi-Quellcode:

procedure bla.init;
begin

Wert1 : Integer = 0; //Initialisieren
Wert2 : Integer = 0;
Ergebnis: Integer = 0;
FBerechnet: Boolean = false;

end;

....

TForm1.buttonclick(Sender: TObject);
var bla: TMeineDaten;

begin

bla:=TMeineDaten.create;
bla.init;

end;
Stimmt das den jetzt, oder ist da was wahres dran? Kann ich jede Art von Klassenvariable im Constructor initialisieren oder
kann es da probleme geben weil die Objecterstellung ja im Constructor noch nicht abgeschlossen ist?

mfG newbe

sirius 15. Okt 2008 07:56

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Die Objekterstellung ist abgeschlossen, wenn du im constrcutor bist.
Der Code zum Erstellen der Instanz fügt der Compiler bei dem Wort "begin" des Constructors ein. Also, wie gesagt, direkt am Anfang.

Edit: Vielleicht mal konkret.
Jede Methode bekommt ja immer mindestens den Parameter "self" übergeben. Damit kannst du ja auf die Felder deiner Klasse zugreifen. Der Constructor bekommt zusätzlich noch einen Wert mitgegeben. Dieser sagt aus, ob der Constructor eine neue Instanz erstellen soll.

Hier mal eine normale Methode
Delphi-Quellcode:
//aus folgendem Code:
procedure TMeineDaten.SetWert1(const Value: Integer);
begin
  FWert1 := Value;  
  FBerechnet:=false;
end;

//wird eigentlich:
procedure TMeineDaten.SetWert1(Self:Pointer; const Value: Integer);
begin
  Self.FWert1 := Value;  
  Self.FBerechnet:=false;
end;
Und self zeigt einfach auf eine Art Record, in dem alle deiner Felder (Klassenvariablen) enthalten sind. Self ist dann auch das, was in deiner Objektinstanz "MeineDaten" (oder in Post #10: "test") steht. Ist halt einfach nur ein Zeiger auf deinen InstanzRecord. Und jede Instanz hat ihren eigenen Record.

So, und jetzt noch mal zum Constrcutor. Der hat, wie gesagt, neben "self" noch einen versteckten Parameter:
Delphi-Quellcode:
//aus folgendem Code:
Constructor TMeineDaten.Create;
begin
  fBerechnet:=false;
end;

//wird dann:
Constructor TMeineDaten.Create(Self:Pointer; doCreateClass:Boolean);
begin
  if doCreateClass then
  begin
    Self:=_ClassCreate(Self, True); //Ab hier existiert dein Objekt.
    Result:=Self;
  end;

  Self.fBerechnet:=false;


  if DoCreateClass then
    _AfterConstruction(Self);
end;
Was macht eigentlich _ClassCreate (neben Exceptionbehandlung und pipapo). Es reserviert einfach Speicher mittels new, wie bei einem normalen Record, den man dynamisch anlegt auch. Und dann wird die Adresse auf den Speicher zurückgegeben.

So, und jetzt gibt es zwei Aufrufmöglichkeiten des Constructors:
Delphi-Quellcode:
test:=TMeindeDaten.create;
Hier wird DoCreateClass auf True gesetzt und anstatt Self wird ein Zeiger auf die TypInformationen von TMeineDaten übergeben (also TMeineDatenClass oder einfach TClass)
Wenn du jetzt den Constructor (irendwann nach der Instanzierung) nochmal so aufrufst (quasi, wie jede andere Methode auch):
Delphi-Quellcode:
test.create;
dann ist DoCreateClass=False und an self wird eben der Wert von test übergeben (wie bei jeder anderen Methode auch).
Nebenbei: Wenn jetzt test noch nicht instanziert wäre und du einfach test.create aufrufst (Was ein typischer Delphi-Anfängerfehler ist), wird für self eben nil (oder ein anderer krummer Wert) übergeben und DoCreateClass ist natürlich false. Dann greifst du hier auf den Record nil.fBerechnet zu, was eine Access Violation, meist in der Nähe von Addresse 0 = nil, auslöst.

Allerdings ist es unüblich test.Create direkt aufzurufen. Der Zustand tritt eher ein, wenn man von TMeineDaten eine neue Klasse ableitet und dort, in einem neuen Constructor, dann inherited aufruft. Dann wird der Vorfahr, was TMeineDaten.Create ist, mit DoClassCreate=False aufgerufen.

Ich hoffe ich habe nicht zu sehr verwirrt. Hier ist es auch nochmal schön beschrieben.

Edit2:
Erst die Klasse instanzieren und dann einen weiteren Befehl zur Initialisierung aufrufen ist in anderen Sprachen (die wir hier nicht erwähnen wollen) notwendig. In Delphi kannst du instanzieren und initialisieren in einem Abwasch, eben im constructor, machen.

newbe 15. Okt 2008 10:06

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
@sirius

Vielen Dank an Dich für deine ausführliche Erklärung dazu. :)

herzliche Grüße

newbe

newbe 15. Okt 2008 15:28

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
@All

Ich habe es jetzt so gemacht.

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;


TMeineDaten = class(TObject)
    private
      fWert1 : Integer;
      fWert2 : Integer;
      FErgebnis: Integer;
      FBerechnet: Boolean;
      procedure SetWert1(const Value: Integer);
      procedure SetWert2(const Value: Integer);
      function GetErgebniss: Integer;
    public
      constructor create;
      property Wert1 : Integer read FWert1 write SetWert1;
      property Wert2 : Integer read FWert2 write SetWert2;
      property Ergebnis : Integer read GetErgebniss;
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}



{ TMeineDaten }

procedure TMeineDaten.SetWert1(const Value: Integer);
begin

  if Value=5 then raise Exception.create('Wert 5 wird nicht als Eingabe akzeptiert!');

  FWert1 := Value;
  FBerechnet := false;
end;

procedure TMeineDaten.SetWert2(const Value: Integer);
begin

  if Value=5 then raise Exception.create('Wert 5 wird nicht als Eingabe akzeptiert!');

  FWert2 := Value;
  FBerechnet := false;
end;

constructor TMeineDaten.create;
begin
  FBerechnet := false;
end;

function TMeineDaten.GetErgebniss: Integer;
begin
  if not FBerechnet then
  begin
    FErgebnis := fWert1 + fWert2;
    FBerechnet := true;
  end;
  result := FErgebnis;
end;

procedure TForm1.Button1Click(Sender: TObject);
var test: TMeineDaten;
begin
  test:=TMeineDaten.create;
  test.Wert1:=strtoint(edit1.text);
  test.Wert2:=strtoint(edit2.text);
  label1.caption:=inttostr(test.ergebnis);
  test.Free;
end;

end.
Erstmal die Frage ob das so OOP Konform durchgeht? Und dann hätte ich da noch eine Frage zur Eingabe-Exeptionbehandlung. Was wäre OOP Konform günstiger,
die Exeptionbehandlung in die Klasse zu integrieren (im Setter) oder wie hier gehabt in einer Controlprocedure der VCL?

Ich hätte die Exeptionbehandlung numal gerne komplett in der Klasse. So wie ich es jetzt mache, kann ich jedoch im Setter nicht prüfen, ob ein leeres
Editfeld vorliegt. Delphi schmeist zwar eine Econvert Error Exeption wegen dem strtoint, jedoch hätte ich gerne eigene Exeptions und dann alle
Bedingungsabfragen in der Klasse. Dazu müsste ich ja dem Setter ein String übergeben, die strtoint Konvertierung ausführen und dann aber einen Integer schreiben. Wäre solch eine vorgehensweise überhaupt Sinnvoll? Wie könntedafür die property Deklaration aussehen? Bei allem was ich probiert habe,
beschwert sich Delphi wegen inkompatibler Typen.

mfG newbe

sirius 15. Okt 2008 17:05

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Wichtig für dich wäre noch try..finally. Das benutzt man wie folgt:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var test: TMeineDaten;
begin
  test:=TMeineDaten.create;
  try
    test.Wert1:=strtoint(edit1.text);
    test.Wert2:=strtoint(edit2.text);
    label1.caption:=inttostr(test.ergebnis);
  finally
    test.Free; //Der Teil wird immer ausgeführt, egal ob Exception kommt oder nicht
  end;
end;
Ansonsten würdest du eine Exception werfen, das PRogramm springt raus aus deinen Methoden und du rufst nie Free auf -> Speicherloch

Exceptions solltest du immer in den unteren Klassen werfen (so wie du es in TMeineDaten gemacht hast) und in der Klasse, welche sowieso für Anzeige (GUI) zuständig ist anzeigen. Die VCL macht das ja schonmal, in dem sie spätestens am Ende der Ereignis-/Messagebehandlung einen Except-Block hat und dann mittels Application.Showexception ein Fenster bringt. Wenn du also am Exceptiontest nichts verändern willst, dann kannst du es so durchlaufen lassen ohne eigene Exceptionbehandlung (try..except).
Ansonsten kann man an dem Beispiel jetzt nicht viel mehr sagen. Ausser dass du in dem speziellen Fall anstatt inttostr auch tryintsotr nehmen kannst um Fehler gleich abzufangen bzw. eine eigene Exception zu kreieren.


Eine übliche Vorgehensweise ist auch in bestimmten Klassen die Exception abzufangen und dann gleich wieder zu werfen, aber mit einer eigenen Ergänzung:
Delphi-Quellcode:
try
 ...
except
  on e:Exception do
    raise Exception.create(e.MEssage+#13#10+'Ich will auch noch was sagen!');
end;
Und nicht zu vergessen auch mal eigene Exceptionklassen zu verwenden.

Blup 17. Okt 2008 10:52

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Man sollte zwischen Eingabefehlern und fachlichen Fehlern unterscheiden.
Ein leeres Eingabefeld sollte im Formular erkannt werden, ein Eingabe der Zahl 0 für einen Divisor aber in der fachlichen Logik.

Die fachliche Logik sollte besser nicht in der Formular-unit implementiert werden (Wiederverwendbarkeit).
Ein OOP-Beispiel ohne Vererbung ist ziemlich sinnfrei, deshalb hier mein Vorschlag:
Delphi-Quellcode:
unit OOP_Calc;

interface

uses
  Classes;

type
  TCustomOperation = class(TObject)
  private
    FWert1: Double;
    FWert2: Double;
    FErgebnis: Double;
    FBerechnet: Boolean;
    function GetErgebniss: Double;
    procedure SetWert(var AWert: Double; AValue: Double);
  protected
    procedure SetWert1(AValue: Double); virtual;
    procedure SetWert2(AValue: Double); virtual;
    function Calc: Double; virtual; abstract;
  public
    property Wert1: Double read FWert1 write SetWert1;
    property Wert2: Double read FWert2 write SetWert2;
    property Ergebnis: Double read GetErgebniss;
  end;

  TSumme = class(TCustomOperation)
  protected
    function Calc: Double; override;
  end;

  TDifferenz = class(TCustomOperation)
  protected
    function Calc: Double; override;
  end;

  TProdukt = class(TCustomOperation)
  protected
    function Calc: Double; override;
  end;

  TQuotient = class(TCustomOperation)
  private
    procedure CheckDivisor(AValue: Integer);
  protected
    procedure SetWert2(AValue: Double); override;
    function Calc: Double; override;
  end;

implementation

const
  ERROR_DIVISORNULL = 'Der Divisor darf nicht Null sein.'

function TCustomOperation.GetErgebniss: Double;
begin
  if not FBerechnet then
  begin
    FErgebnis := Calc;
    FBerechnet := True;
  end;
  Result := FErgebnis;
end;

procedure TCustomOperation.SetWert(var AWert: Double; AValue: Double);
begin
  if AWert <> AValue then
  begin
    AWert := AValue;
    FBerechnet := False;
  end;
end;

procedure TCustomOperation.SetWert1(AValue: Double);
begin
  SetWert(FWert1, AValue);
end;

procedure TCustomOperation.SetWert2(AValue: Double);
begin
  SetWert(FWert2, AValue);
end;

function TSumme.Calc: Double;
begin
  Result := FWert1 + FWert2;
end;

function TDifferenz.Calc: Double;
begin
  Result := FWert1 - FWert2;
end;

function TProdukt.Calc: Double;
begin
  Result := FWert1 * FWert2;
end;

procedure TQuotient.CheckDivisor(AValue: Integer);
begin
  if AValue = 0 then
    raise Exception.Create(ERROR_DIVISORNULL);
end;

function TQuotient.Calc: Double;
begin
  CheckDivisor(FWert2);
  Result := FWert1 / FWert2;
end;

procedure TQuotient.SetWert2(AValue: Double);
begin
  CheckDivisor(AValue);
  inherited SetWert(FWert2, AValue);
end;

Apollonius 17. Okt 2008 11:04

Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
 
Calc sollten allerdings die Operanden übergeben werden, sonst lässt sich die Klasse nur innerhalb der Unit sinnvoll ableiten.


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:53 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz