Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Objektorientiertes Programmieren


baphumet - Do 08.10.09 14:51
Titel: Objektorientiertes Programmieren
Hi,

ich plane ein Programm, das Flächen und Volumen von Körpern berechnet, dies kann ich auch ohne Objektorientjeurng lösen, aber es bietet sich an finde ich. Denn jeder Körper hat eine Oberfläche und ein Volumen, deshalb fällt mir ein das ich eine Klasse TKoerper deklariere, von dieser sind die restlichen abgeleitet und dort die Methoden für die Berechnungen implementiert, deshalb sollen auch die Attribute in den jeweilgen Klassen stehen. Aber ich bräuchte ja nur eine Ausgabemethode in TKoerper, wie kann ich dies realisieren das die Berechnung in den abgeleiteten Klassen ausgeführt wird und ich in der Ausgabemethode von TKoerper auf die Attribute der abgeleiteten Klassen zugreifen kann.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
TKoerper=class
private
public
  fncton Volumen():EXTENDED; virtual;abstract;
  procedure Oberflaeche():EXTENDED; virtual;abstract;
  procedure Ausgabe//wie realisiere ich das
end;
TWuerfel=class(TKoerper)
private
  a,erg: EXTENDED;
public
  constructor Create(...);
  procedure Volumen() overrride;
  procedure Oberflaeche() override;
end;
TQuader=class(TKörper)
private
  a,b,erg: EXTENDED;
public
  constructor Create(...);
  procedure Volumen(); override
  procedure Oberflaeche(); override;

TPyramide=class(TKoerper)
private
  a,h,erg: EXTENDED;
public
  constructor Create(...);
  procedure Volumen(); override;
  procedure Oberflaeche(); override;
end;
TPyramidenstumpf=class(TKoerper)
private
  a,h,erg: EXTENDED;
public
  constructor Create(...);
  procedure Volumen(); override;
  procedure Oberflaeche(); override;
end;
TKegel=class(TKoerper)
private
...
end;
TKegelstumpf(TKoerper)
private
...
end;


In meiner anderen Unit soll es wie folgt aussehen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var
  Koerper: TKoerper;
begin
  Koerper:=TWuerfel.Create(5);
  Koerper.Volumen;
  Koerper.Oberflaeche;

  //Anschließende Ausgabe

end;


Boldar - Do 08.10.09 14:55

Warum die Attribute der abgeleiteten Klassen? Volumen und Oberfläche ist doch schon in TKörper definiert?? :gruebel: :nixweiss:


Lossy eX - Do 08.10.09 15:10

Anstelle von dem Overload meinst du vermutlich Override, oder? Virtual Abstract dient dazu um eine Methode zu definieren die woanders mit Inhalt gefüllt wird.

Zur Ausgabe. Kommt natürlich immer darauf an wohin die Ausgabe gehen soll. Wenn es darum geht die beiden Werte in Labels zu packen, dann kannst du auch direkt auf die Rückgabewerte von den Methoden Volumen und Oberflaeche zurückgreifen. Da alle Klassen/Objekte solch eine Methode anbieten müssen sollte das kein Problem machen. Dann kannst du diese Ausgabe irgendwo machen wo du die eine Instanz der Klasse hin übergeben hast.


Delphi-Quelltext
1:
2:
Label1.Caption := FlotToStr(Koerper.Volumen);
Label2.Caption := FlotToStr(Koerper.Oberfläche);

So als einfachstes Beispiel. Sonst kommt es dann natürlich darauf an wohin du es ausgeben willst. Wobei ich die Ausgabe schon eher von den berechnen trennen würde. Es sei denn es wäre so etwas wie ein Objekt zeichnen und das könnte dann natürlich nur jedes Objekt selber machen. Also wie die Berechnungen.


mkinzler - Do 08.10.09 15:29

Statt FlotToStr() muss es FloatToStr() oder noch besser Format()


Lossy eX - Do 08.10.09 15:37

user profile iconmkinzler hat folgendes geschrieben Zum zitierten Posting springen:
Statt FlotToStr() muss es FloatToStr() oder noch besser Format()

Vielen Dank für die wunderbare Verbesserung. Du hast meinen Tag gerettet.


mkinzler - Do 08.10.09 15:39

War nicht als Kritik gedacht; war ja auch nur ein Tippfehler


Heiko - Do 08.10.09 23:25

user profile iconmkinzler hat folgendes geschrieben Zum zitierten Posting springen:
oder noch besser Format()

Warum Format? Form,at verschlechtert das ganze doch nur noch. FloatToStr greift auch nur auf Format zurück - ist allerdings schneller lesbar als ein Formatstring (nicht jeder kennt alle Buchstaben auswendig). Aus diesem Grund Format nur dann nehmen, wenn es wirklich Vorteile bringt.


mkinzler - Fr 09.10.09 06:33

Z.B. bei der Ausgabe in einem Label


Lossy eX - Fr 09.10.09 08:07

Markus. Also bei so einem dahin geworfenen Fetzen wird dich nur wohl nur jemand verstehen der es sowieso schon weiß. Um deiner Aussage also ein bisschen Substanz zu geben...

Du kannst bei Format sehr einfach angeben wie viele Nachkommastellen benutzt werden sollen. Also ein Formatstring wie '%0.3f' würde genau 3 Nachkommastellen erzeugen. Falls die Zahl Rund ist, dann würden 3 Nullen angestellt werden. Mit FloatToStr würdest du immer alle Nachkommastellen bekommen. Also entweder gar keine oder im Fall von sehr krummen Zahlen locker mal 10 oder mehr. Für eine ein sinnvolle Ausgabe sind die nur selten Relevant. Bzw sieht es mit einheitlichen Nachkommastellen auch einfach besser aus.


mkinzler - Fr 09.10.09 08:10

Ich gehe halt einfach davon aus, das der Fragesteller die Hilfe bedienen kann und auch willens ist dieses auch zu tun.


Lossy eX - Fr 09.10.09 09:18

Auch auf die Gefahr hin, dass mein Post gelöscht wird. Die Hilfe in den neuen Delphis ist ziemlich grausam. Und eine ernsthafte Frage mit einer solchen nichts sagenden Antwort abzuspeisen finde ich unhöflich und arogant. Denn helfen tut das nun wirklich nicht. Entsprechend könnte man es auch gleich lassen.


mkinzler - Fr 09.10.09 09:20

Dann schau dir mal dies an http://www.delphipraxis.net/viewtopic.php?t=165686


baphumet - Fr 09.10.09 12:55

Ich kann die Hilfe lesen und ich komm mit der Organisation auch zurecht, an manchen Stellen ist Sie aber etwas mager finde ich und Willens bin ich alle Mal. Natürlich gebe ich dir Recht das man versucht die Berechnung und die Ausgabe soweit möglichst voneinander zu entkoppeln, das ist auch meine Idee. Ich wollte ja nur nachfragen ob mein Ansatz für das Projekt gut durchdacht ist oder ob es etwaige Verbesserungsvorschläge gibt.

Ich programmiere schon seit mehr als 10 Jahren mit Pascal oder später auch mit Dephi und ich kenne zwar nicht alle Befehle die Delphi bietet, ich glaube das wäre auch zu viel verlangt aber die Grundbefehle sind mir schon bekannt, alleine bei jeder Komponente gibt es Unmengen von Methoden. Ich habe nichts gegen English wäre aber gut wenn die Delphi Hilfe Multilinugal wäre, also in den geläufigsten Sprachen von Delphi mitgeliefert wird und bei der Installation die entsprechende(n) Sprachen ausgewählt werden können(Deutsch/Englisch/Französisch/Spanisch/Portugiesisch/Italienisch/Polnisch/Russisch)


Boldar - Fr 09.10.09 13:25

Also meine Hilfe ist auf Deutsch


Lossy eX - Fr 09.10.09 14:54

Bei mir verweigert die Hilfe regelmäßig den Dienst und speziell solche Dinge die die Sprache betreffen scheinen in der Hilfe seit Delphi 2006 oder 2007 irgendwie komplett abhanden bzw in der hinterletzten Ecke versteckt worden zu sein.

@baphumet: Das mit willens war auch nicht auf dich bezogen. Sondern eher eine Reaktion auf meine Kritik bezüglich Bröckchenposts.

Zu dem Design deiner Klassen. Also für meinen Geschmack sieht das schon ganz okay aus. Wobei ich natürlich so keine Ahnung habe was du mit den Klassen noch so vor hast. Das Design richtet sich immer danach was man damit anstellen will. Allerdings was die Objektorientierung generell angeht. Da solltest du immer schauen was die Objekte so für Gemeinsamkeiten haben. Als Beispiel mal Würfel und Quader. Ein Würfel ist eine Spezialform eines Quaders. Oder anders. Jeder Würfel ist auch automatisch ein Quader auch wenn dann immer nur von einem Würfel gesprochen wird. Die Berechnungen sind auch gleich. Entsprechend könntest du TWuerfel auch von TQuader ableiten.

Das wäre die Deklarartion

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
TQuader = class(TKoerper)
private
public
  constructor Create(a, b, c: Double);
  procedure Oberflaeche(); override;
end;

TWuerfel = class(TQuader)
public
  constructor Create(a: Double);
end;


Und so könnte der Konstruktor aussehen.

Delphi-Quelltext
1:
2:
3:
4:
constructor TWuerfel.Create(a: Double);
begin
  inherited Create(a, a, a);
end;


Der "Trick" hierbei ist einfach nur, dass ein Würfel ein Quader mit identischen Seitenlängen ist. Entsprechend sorge ich bei der Erstellung nur dafür, dass die Vorfahrenklasse (TQuader) identische Seitenlängen bekommt. So kannst du auch Code sparen. Weil du Redundanzen weglässt. Außerdem ist der Code weniger Fehlanfälliger. Wenn deine Berechnungen für einen Quader stimmen, dann stimmen sie auch für den Würfel.

Eventuell könnte man so auch bei der Pyramide/Pyramidenstumpf und Kegel/Kegelstumpf vorgehen. Muss ich aber passen. Da weiß ich nicht mehr ob man das Sinnvoll unter einen Hut bringen kann. Wobei man Stumpf auch von Kegel ableiten könnte und trotdem alle Berechnungen seperat implementiert. Um eine "Kategoriesierung" der Klassen zu erreichen. Wenn es Zeitkritisch ist, dann lohnt es vielleicht auch gar nicht die Klassen zu sehr zu verbiegen. Wenn es dadurch deitlich Langsamer werden würde. (Bei sehr vielen Instanzen der Klassen zum Beispiel)


delfiphan - Fr 09.10.09 15:05

Angenommen deine Würfel/Quader Objekte sind nicht immutable, kannst du nicht so vorgehen. Entweder verletzst du das Liskovsche Substitutionsprinzip oder dein Würfel ist plötzlich kein Würfel mehr.
Quader/Würfel bzw. Ellipse/Kreis sind klassische Beispiele, um zu zeigen, dass es problematisch ist Quader von Würfel (Erweiterung) oder Würfel von Quader (Spezialisierung) abzuleiten.

Für die Berechnung würde ich nicht extra eine grosse Menge an Klassen schreiben. Denn das Objekt, das du kreierst ist für nichts. Es ist bei dir nur ein Nebenprodukt; was du willst ist eine Berechnung.

Meine Meinung, aber mach lieber eine CalculateCubeVolume(a: Double): Double Funktion, die dann CalculateCuboidVolume(a, b, c: Double): Double aufruft. Das ist kürzer, klarer, und ressourcenschonender.

Wenn das Objekt nicht nur als "Nebenprodukt" anfällt sondern auch zum Speichern oder Anzeigen, etc. dann ist es was anderes.