![]() |
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. |
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. |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
hi,
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Gruß Ansgar |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Zitat:
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. |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Zitat:
|
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Zitat:
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: |
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:
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. |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Zitat:
|
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Stimmt, fällt im Beispiel nur nicht auf, weil es in derselben Unit steht.
|
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. |
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.
|
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:
[/edit]
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; |
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: |
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 |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Delphi-Quellcode:
Ist mir zu lang, dass jetzt zu erklären.
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; FBerechnet könnte man noch im constrcutor initialisieren. |
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:
Stimmt das den jetzt, oder ist da was wahres dran? Kann ich jede Art von Klassenvariable im Constructor initialisieren oderprocedure 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; kann es da probleme geben weil die Objecterstellung ja im Constructor noch nicht abgeschlossen ist? mfG newbe |
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:
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.
//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; So, und jetzt noch mal zum Constrcutor. Der hat, wie gesagt, neben "self" noch einen versteckten Parameter:
Delphi-Quellcode:
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.
//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; So, und jetzt gibt es zwei Aufrufmöglichkeiten des Constructors:
Delphi-Quellcode:
Hier wird DoCreateClass auf True gesetzt und anstatt Self wird ein Zeiger auf die TypInformationen von TMeineDaten übergeben (also TMeineDatenClass oder einfach TClass)
test:=TMeindeDaten.create;
Wenn du jetzt den Constructor (irendwann nach der Instanzierung) nochmal so aufrufst (quasi, wie jede andere Methode auch):
Delphi-Quellcode:
dann ist DoCreateClass=False und an self wird eben der Wert von test übergeben (wie bei jeder anderen Methode auch).
test.create;
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. ![]() 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. |
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 |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
@All
Ich habe es jetzt so gemacht.
Delphi-Quellcode:
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,
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. 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 |
Re: kleines OOP Beispiel bitte um Anmerk./Verbesserungvorsch
Wichtig für dich wäre noch try..finally. Das benutzt man wie folgt:
Delphi-Quellcode:
Ansonsten würdest du eine Exception werfen, das PRogramm springt raus aus deinen Methoden und du rufst nie Free auf -> Speicherloch
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; 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:
Und nicht zu vergessen auch mal eigene Exceptionklassen zu verwenden.
try
... except on e:Exception do raise Exception.create(e.MEssage+#13#10+'Ich will auch noch was sagen!'); end; |
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; |
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