Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Übung Polymorphie (https://www.delphipraxis.net/194429-uebung-polymorphie.html)

EdAdvokat 19. Nov 2017 16:14

Übung Polymorphie
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo, ich wage mich mal wieder ins Forum mit der Bitte, über meine kleine Übung zur Polymorphie zu sehen und mir ggf. einen Tip zu geben ob und wo ich was nicht richtig gemacht habe.
Auf einige Kleinigkeiten, wie der Prüfung ob die Werte größer 0 bzw. nur Zahlen sein dürfen habe ich hier verzichtet.
Meine Frage ist: habe ich das Prinzip der Polymorphie in Form der Klassen (TFigur, TRechteck, TDreieck) so richtig verstanden?
Vielen Dank im voraus.
Programm anbei

Delphi-Quellcode:
unit uBerechnung;

interface

uses Vcl.Dialogs;

type TFigur = class(TObject)
  private
    FSeiteA:Double;
    FSeiteB:Double;
    procedure SetSeiteA(A:Double);virtual;abstract;
    procedure SetSeiteB(B:Double);virtual;abstract;

    function getSeiteA:Double;virtual;abstract;
    function getSeiteB:Double;virtual;abstract;
    function getBerechneFl:Double;virtual;abstract;
end;

type TRechteck = class(TFigur)
  private
    procedure SetSeiteA(A:Double);override;
    procedure SetSeiteB(B:Double);override;
    function getSeiteA:Double;override;
    function getSeiteB:Double;override;
    function getBerechneFl:Double; override;
   public
    property SeiteA:Double read FSeiteA write FSeiteA;
    property SeiteB:Double read FSeiteB write FSeiteB;
    property BerechneFl:Double read getBerechneFl;
end;

type TDreieck = class(TRechteck)
  private
    procedure SetSeiteA(A:Double);override;
    procedure SetSeiteB(B:Double);override;
    function getSeiteA:Double;override;
    function getSeiteB:Double;override;
    function getBerechneFl:Double;override;
   public
    property SeiteA:Double read FSeiteA write FSeiteA;
    property SeiteB:Double read FSeiteB write FSeiteB;
    property BerechneFl:Double read getBerechneFl;
end;

implementation

{ TRechteck }


function TRechteck.getBerechneFl: Double;
begin
  result:=FSeiteA*FSeiteB;
end;

function TRechteck.getSeiteA: Double;
begin
  result:=FSeiteA;
end;

function TRechteck.getSeiteB: Double;
begin
  result:=FSeiteB;
end;

procedure TRechteck.SetSeiteA(A: Double);
begin
  FSeiteA:=A;
end;

procedure TRechteck.SetSeiteB(B: Double);
begin
  FSeiteB:=B;
end;


{ TDreieck }

function TDreieck.getBerechneFl: Double;
begin
  result:=FSeiteA*FSeiteB/2;
end;

function TDreieck.getSeiteA: Double;
begin
  result:=FSeiteA;
end;

function TDreieck.getSeiteB: Double;
begin
  result:=FSeiteB;
end;

procedure TDreieck.SetSeiteA(A: Double);
begin
  FSeiteA:=A;
end;

procedure TDreieck.SetSeiteB(B: Double);
begin
  FSeiteB:=B;
end;

end.
und die Form:
Delphi-Quellcode:
unit uMain;

interface

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

type
  TForm1 = class(TForm)
    btnEnde: TButton;
    edtSeiteA: TEdit;
    edtSeiteB: TEdit;
    lblSeiteA: TLabel;
    lblSeiteB: TLabel;
    lblErg: TLabel;
    btnFl: TButton;
    btnDreieck: TButton;
    procedure btnEndeClick(Sender: TObject);
    procedure btnFlClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btnDreieckClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnDreieckClick(Sender: TObject);
var MyFigure: TDreieck;
begin
  MyFigure := TDreieck.Create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    lblErg.caption:=floattostr(MyFigure.BerechneFl)
  finally
    MyFigure.Free;
  end;
end;

procedure TForm1.btnEndeClick(Sender: TObject);
begin
  close;
end;

procedure TForm1.btnFlClick(Sender: TObject);
var MyFigure : TRechteck;
begin
  MyFigure := TRechteck.Create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    lblErg.caption:=floattostr(MyFigure.BerechneFl);
  finally
    MyFigure.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  edtSeiteA.text:='';
  edtSeiteB.text:='';
end;

end.

DeddyH 19. Nov 2017 17:07

AW: Übung Polymorphie
 
Deklariere Deine lokalen Variablen in den Form-Methoden einmal als TFigur. Wenn der Code dann funktioniert, ist das Polymorphie (etwas vereinfacht, aber im Kern richtig).

EdAdvokat 19. Nov 2017 17:39

AW: Übung Polymorphie
 
nachdem ich in der class TFigur eine Ergänzung vorgenommen habe und dann in der Form die lokale Var in TFigur umbenannt habe geht es auch.
Zuvor hatte das Programm jedoch auch keine Probleme.
Delphi-Quellcode:
type TFigur = class(TObject)
  private
    FSeiteA:Double;
    FSeiteB:Double;
    procedure SetSeiteA(A:Double);virtual;abstract;
    procedure SetSeiteB(B:Double);virtual;abstract;

    function getSeiteA:Double;virtual;abstract;
    function getSeiteB:Double;virtual;abstract;
    function getBerechneFl:Double;virtual;abstract;
  public
    property SeiteA:Double read FSeiteA write FSeiteA;
    property SeiteB:Double read FSeiteB write FSeiteB;
    property BerechneFl:Double read getBerechneFl;
Danke.
Also habe ich das Problem wohl richtig verstanden?!

DeddyH 19. Nov 2017 18:12

AW: Übung Polymorphie
 
Ich würde die virtuellen Methoden als protected deklarieren, sonst gibt es Kummer, wenn Du die Ableitungen einmal in eine eigene Unit verschiebst. Und Polymorphie bedeutet ja Vielgestaltigkeit, d.h. der konsumierende Code muss die konkrete Klasse, die er benutzt, gar nicht kennen. Es genügt, wenn er die Basisklasse kennt, ihn interessiert es dabei dann nicht mehr, um welche Ableitung es sich handelt.

EdAdvokat 19. Nov 2017 18:25

AW: Übung Polymorphie
 
habe den Hinweis berücksichtigt, jedoch erhalte ich den folgenden Hinweis:
Zitat:

[dcc32 Hinweis] uBerechnung.pas(25): H2269 Durch das Überschreiben erhält die virtuelle Methode 'TRechteck.SetSeiteA' eine geringere Sichtbarkeit (private) als die Basisklasse 'TFigur' (protected)
Damit kann man sicher leben.

Aviator 19. Nov 2017 18:44

AW: Übung Polymorphie
 
Schreibe den Setter ebenfalls in den Protected Abschnitt und dann ist auch die Warnung weg. Warnungen solltest du schon beachten und auch entsprechend versuchen sie zu beheben. Nicht einfach damit leben. :cyclops:

EdAdvokat 19. Nov 2017 18:55

AW: Übung Polymorphie
 
Ok. Setter ist auch protected. Nun habe ich immer noch eine Warnung:
Zitat:

[dcc32 Warnung] uBerechnung.pas(44): W1045 Eigenschaftsdeklaration verweist auf private-Vorfahr 'TFigur.FSeiteA'
FSeiteA und FSeiteB, die ebenfalls diese Warnung erhält habe ich nur in der class TFigur declariert. Sollte ich diese beiden Felder jeweils auch in die abgeleiteten class aufnehmen als private?

hoika 20. Nov 2017 08:56

AW: Übung Polymorphie
 
Hallo,
nein, dann wären die ja in der Basisklasse überflüssig.
Ich würde den Tip von DeddyH einfach wieder zurücknehmen oder deine privat-Variablen landen
auch in der Basisklasse unter protected.

Das wäre dann zwar ein schönes Beispiel für Polymorphie, aber nicht für Klassendesign.
Dort sollten ja Variablen, die die abgeleitete Klasse nicht zu interessieren hat, privat sein.

DeddyH 20. Nov 2017 09:47

AW: Übung Polymorphie
 
Ich habe das Ganze mal umgeschrieben:
Delphi-Quellcode:
type
  TFigur = class
  private
    FSeiteB: Double;
    FSeiteA: Double;
  protected
    function GetFlaeche: double; virtual; abstract;
  public
    property SeiteA: Double read FSeiteA write FSeiteA;
    property SeiteB: Double read FSeiteB write FSeiteB;
    property Flaeche: double read GetFlaeche;
  end;

  TRechteck = class(TFigur)
  protected
    function GetFlaeche: double; override;
  end;

  TDreieck = class(TRechteck)
  protected
    function GetFlaeche: double; override;
  end;

...

{ TRechteck }

function TRechteck.GetFlaeche: double;
begin
  Result := SeiteA * SeiteB;
end;

{ TDreieck }

function TDreieck.GetFlaeche: double;
begin
  Result := SeiteA * SeiteB / 2;
end;
Formular:
Delphi-Quellcode:
type
  TForm6 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
    procedure ShowFlaeche(Figur: TFigur);
  public
    { Public-Deklarationen }
  end;

...

procedure TForm6.Button1Click(Sender: TObject);
var
  Figur: TFigur;
begin
  Figur := TRechteck.Create;
  try
    Figur.SeiteA := 4;
    Figur.SeiteB := 5;
    ShowFlaeche(Figur);
  finally
    Figur.Free;
  end;
end;

procedure TForm6.Button2Click(Sender: TObject);
var
  Figur: TFigur;
begin
  Figur := TDreieck.Create;
  try
    Figur.SeiteA := 4;
    Figur.SeiteB := 5;
    ShowFlaeche(Figur);
  finally
    Figur.Free;
  end;
end;

procedure TForm6.ShowFlaeche(Figur: TFigur);
begin
  ShowMessage(Format('Die Fläche beträgt %.2f', [Figur.Flaeche]));
end;

freimatz 20. Nov 2017 10:08

AW: Übung Polymorphie
 
Von mir:
- in unit uBerechnung kein uses Vcl.Dialogs
- statt "privat" besser "strict privat" und "strict" auch bei "protected"

EdAdvokat 20. Nov 2017 12:21

AW: Übung Polymorphie
 
Danke Hoika, ja so funktioniert es.

Danke auch an Freimatz, vcl.dialogs habe ich aufgenommen, da ich eigentlich mit showmessage arbeiten wollte. Hat sich aber erledigt. Warum soll ich ausdrücklich "strict" private bzw. strict protected verwenden? reicht private bzw. protected nicht aus? was ist da anders bzw. besser?

Danke DaddyH, das ist wesentlich einfacher und übersichtlicher. Warum bin ich nicht darauf gekommen? Ich habe mich von dem Gedöns Getter und Setter verleiten lassen.
Aber so einfach funktioniert es auch einwandfrei und vermeidet Stolpersteine.
Wieder ein kleines Problemchen gelöst.

SProske 20. Nov 2017 12:29

AW: Übung Polymorphie
 
Zitat:

Zitat von EdAdvokat (Beitrag 1386701)
Warum soll ich ausdrücklich "strict" private bzw. strict protected verwenden? reicht private bzw. protected nicht aus? was ist da anders bzw. besser?

Ohne das strict haben auch andere Klassen innerhalb der selben Unit Zugriff, also TRechteck auf private Member von TDreieck und umgekehrt.

freimatz 20. Nov 2017 12:38

AW: Übung Polymorphie
 
Genau das ist es. Das ist bei deinem Problem nur eine Kleinigkeit sollte man sich jedoch angewöhnen. Siehe auch: https://de.wikipedia.org/wiki/Datenk...rogrammierung)

EdAdvokat 20. Nov 2017 14:02

AW: Übung Polymorphie
 
nun wollte ich das ganze vertiefen und habe dazu eine weitere class TTrapez (TDreieck) hinzugefügt. Für die Flächenberechnung wird nun zusätzlich zu SeiteA und SeiteB noch eine Höhe benötigt. Wenn ich nun in meiner Naivität einfach zur geerbten class TTrapez das Feld FHoehe und das Property Hoehe
hinzufüge im Glauben, dass ich zum Geerbten einfach noch die zusätzlich benötigten Felder und Propertys für eine erfolgreiche Programmausführung hätte, habe ich mich gründlich getäuscht, denn er findet die Poperty Hoehe in der Form nicht.
Muss ich also diese Ergänzungen für die Flächenberechnung eines Trapezes in die Basisklasse TFigur aufnehmen damit ich weiter komme?
Eigentlich wollte ich sehen wie es mit der Vererbung klappt, doch so würde ich zwar auch mit dem Ergebnis der Vererbung von der Basisklasse arbeiten.
Schön wäre es, wenn ich mit dem Erbe von TDreieck und den zusätzlich benötigten Feldern arbeiten könnte. Liege ich da völlig falsch?
Wie sollte ich das anstellen.
Anbei die Auszüge aus der unit Berechnung und unit Form:
Delphi-Quellcode:
type TFigur = class(TObject)
  private
    FSeiteA:Double;
    FSeiteB:Double;
  strict protected
    function getBerechneFl:Double;virtual;abstract;
  public
    property SeiteA:Double read FSeiteA write FSeiteA;
    property SeiteB:Double read FSeiteB write FSeiteB;
    property BerechneFl:Double read getBerechneFl;
end;

type TRechteck = class(TFigur)
  strict protected
    function getBerechneFl:Double;override;
 end;

type TDreieck = class(TRechteck)
  strict protected
    function getBerechneFl:Double;override;
 end;

type TTrapez = class (TDreieck)
  private
    FHoehe : Double;
  strict protected
    function getBerechneFl: Double;override;
  public
    property Hoehe :Double read FHoehe write FHoehe;
end;

implementation

{ TRechteck }

function TRechteck.getBerechneFl: Double;
begin
  result:=FSeiteA*FSeiteB;
end;

{ TDreieck }

function TDreieck.getBerechneFl: Double;
begin
  result:=FSeiteA*FSeiteB/2;
end;
{ TTrapez }

function TTrapez.getBerechneFl: Double;
begin
  result:=(SeiteA+SeiteB)/2*Hoehe;
end;

end.
und die Form:
Delphi-Quellcode:
procedure TForm1.btnDreieckClick(Sender: TObject);
var MyFigure: TFigur;
begin
  MyFigure := TDreieck.Create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    lblErg.caption:=floattostr(MyFigure.BerechneFl)
  finally
    MyFigure.Free;
  end;
end;

procedure TForm1.btnEndeClick(Sender: TObject);
begin
  close;
end;

procedure TForm1.btnFlClick(Sender: TObject);
var MyFigure : TFigur;
begin
  MyFigure := TRechteck.Create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    lblErg.caption:=floattostr(MyFigure.BerechneFl);
  finally
    MyFigure.Free;
  end;
end;

procedure TForm1.btnTrapezClick(Sender: TObject);
var MyFigure : TFigur;
begin
  MyFigure := TTrapez.create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    //MyFigure //Problem: findet Hoehe aus TTrapez nicht!
    lblErg.caption:=floattostr(MyFigure.BerechneFl);
  finally
    MyFigure.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  edtSeiteA.text:='';
  edtSeiteB.text:='';
end;

end.

DeddyH 20. Nov 2017 14:26

AW: Übung Polymorphie
 
MyFigure ist als TFigur deklariert, deshalb kannst Du auch nur dessen Methoden und Eigenschaften direkt nutzen. Für spezifische Sachen musst Du typecasten.
Delphi-Quellcode:
procedure TForm1.btnTrapezClick(Sender: TObject);
var MyFigure : TFigur;
begin
  MyFigure := TTrapez.create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    TTrapez(MyFigure).Hoehe := 20; // Um die Hoehe ansprechen zu können ist ein Cast notwendig
    lblErg.caption:=floattostr(MyFigure.BerechneFl);
  finally
    MyFigure.Free;
  end;
end;

EdAdvokat 20. Nov 2017 14:42

AW: Übung Polymorphie
 
Danke DaddyH, es funktioniert.
Nur zum Selbstverständnis: wollte ich eine geerbte class TTrapez (TDreieck) um irgendwelche Felder und ggf. auch Methoden ergänzen, so müßte ich also die lokale Variable MyFigure als TTrapez deklarieren und könnte somit auf die Felder und Methoden zugreifen? Entspricht dies auch den guten Sitten der OOP?

DeddyH 20. Nov 2017 14:48

AW: Übung Polymorphie
 
Ja, wieso denn nicht? Der Cast wäre unnötig gewesen, wenn Du die lokale Variable gleich als TTrapez deklariert hättest, aber das wäre ja am Thema vorbei. Du kannst auch beispielsweise in Routinen, die eine TFigur als Parameter entgegennehmen, prüfen, ob es sich um eine spezialisierte Klasse handelt und sie dementsprechend behandeln.
Delphi-Quellcode:
procedure MachWas(Figur: TFigur);
begin
  if Figur is TTrapez then
    TTrapez(Figur).Hoehe := 20;
end;
Das kann aber leider auch sehr schnell zu sehr unübersichtlichem Code führen, in dem Fall sollte man vielleicht sein Klassendesign überdenken und/oder die Verwendung von Interfaces in Betracht ziehen.

nahpets 20. Nov 2017 15:01

AW: Übung Polymorphie
 
Statt diesem
Delphi-Quellcode:
procedure TForm1.btnTrapezClick(Sender: TObject);
var MyFigure : TFigur;
begin
  MyFigure := TTrapez.create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    TTrapez(MyFigure).Hoehe := 20; // Um die Hoehe ansprechen zu können ist ein Cast notwendig
    lblErg.caption:=floattostr(MyFigure.BerechneFl);
  finally
    MyFigure.Free;
  end;
end;
würd' ich lieber das machen:
Delphi-Quellcode:
procedure TForm1.btnTrapezClick(Sender: TObject);
var MyFigure : TTrapez;
begin
  MyFigure := TTrapez.create;
  try
    MyFigure.SeiteA:=strtofloat(edtSeiteA.text);
    MyFigure.SeiteB:=strtofloat(edtSeiteB.text);
    MyFigure.Hoehe := 20;
    lblErg.caption:=floattostr(MyFigure.BerechneFl);
  finally
    MyFigure.Free;
  end;
end;
Casten würd' ich in soeiner Situation:
Delphi-Quellcode:
function FlaecheBerechnen(Sender: TFigure) : Double;
begin
  if Sender is TTrapez then Result := TTrapez(Sender).BerechneFl
  else
  if Sender is TRechteck then Result := TRechteck(Sender).BerechneFl
  else
  if Sender is TDreieck then Result := TDreieck(Sender).BerechneFl
  else
  if Sender is TFigur then Result := TFigur(Sender).BerechneFl
  else Raise('unbekannte Figur');
end;

// Aufruf:
procedure TForm1.BtnBerechnenClick(Sender: TObject);
begin
  MyFigure := TTrapez.create; // der gewünschte Typ, den könnte man z. B. über 'n TRadioGroup auswählen.
  try
    MyFigure.SeiteA := strtofloat(edtSeiteA.text);
    MyFigure.SeiteB := strtofloat(edtSeiteB.text);
    MyFigure.Hoehe := strtofloat(edtHoehe.text);
    lblErg.caption := floattostr(FlaecheBerechnen(myFigure));
  finally
    MyFigure.Free;
  end;
end;

DeddyH 20. Nov 2017 15:14

AW: Übung Polymorphie
 
Wozu die ganzen "Is"-Abfragen in FlaecheBerechnen, wenn eine TFigur-Instanz übergeben wird? Die angesprochene Methode ist virtuell, es sollte also immer die passende ausgeführt werden.

nahpets 20. Nov 2017 15:22

AW: Übung Polymorphie
 
Hast recht, wäre nur dann erforderlich, wenn sie nicht von einer Basisklasse abgeleitet wären.


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