Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Beitrag von fliegermichl »

In diesem Beitrag hatte Theo einen Link zu einer Einführung in OOP gepostet. Dort werden als praktisches Beispiel geometrische Objekte als Klassen definiert.
Das ist eine gute Idee und ich würde das gerne aufgreifen.

Wir nehmen uns vor, eine einfach 2D CAD Anwendung zu programmieren.
Allen CAD Objekten gleich ist, daß sie eine Bezeichnung, eine Farbe sowie eine X und eine Y Position am Bildschirm haben sollen.
Man könnte das so definieren.

Code: Alles auswählen

type
  TGeo = class
    Bezeichnung : string;
    Farbe : TColor;
    x, y : integer;
  end;
Diese Variablen werden bei Klassen Felder genannt.
Da man anhand der Bezeichner nicht erkennen kann, ob es sich um Felder handelt, hat sich eingebürgert, daß diesen immer ein f vorangestellt wird.
Sieht dann also jetzt so aus:

Code: Alles auswählen

type
  TGeo = class
    fBezeichnung : string;
    fFarbe : TColor;
    fx, fy : integer;
  end;
Nun sollen CAD Objekte ja auch gezeichnet werden können. Dazu soll zunächst die aktuelle Stiftfarbe und Position gesichert werden, Dann soll sich das Geoobjekt darstellen und
danach die gesicherte Farbe und Position wieder hergestellt werden.

Damit kommen die sogenannten Methoden ins Spiel. Methoden sind Prozeduren und Funktionen die zu einer Klasse definiert werden und automatisch immer einen unsichtbaren Parameter self bekommen, welcher auf die Instanz der Klasse verweist. Methoden können immer auf die Felder zugreifen. Der Compiler sorgt dafür, daß es immer die richtigen sind. Um die Stiftfarbe und Position sichern zu können, definieren wir zwei neue Felder fSavePos und fSaveColor sowie 3 Methoden zum sichern, Darstellen und restaurieren.

Code: Alles auswählen

type

  { TGeo }

  TGeo = class
    fBezeichnung : string;
    fFarbe : TColor;
    fx, fy : integer;
    fSaveColor : TColor;
    fSavePosition : TPoint;
    procedure SaveCanvas(Canvas : TCanvas);
    procedure Draw(Canvas : TCanvas); virtual; abstract;
    procedure RestoreCanvas(Canvas : TCanvas);
  end;

implementation

{ TGeo }

procedure TGeo.SaveCanvas(Canvas: TCanvas);
begin
  fSaveColor := Canvas.Pen.Color;
  fSavePosition := Canvas.PenPos;
end;

procedure TGeo.RestoreCanvas(Canvas: TCanvas);
begin
  Canvas.Pen.Color := fSaveColor;
  Canvas.PenPos := fSavePosition;
end;
Zu den Methoden SaveCanvas und RestoreCanvas gibt es glaube ich nicht viel zu erklären. Die erste bekommt eine Variable der Instanz einer TCanvas Klasse und sichert deren Stiftfarbe sowie Position in den neuen Feldern und RestoreCanvas macht genau das Gegenteil.

Aber was hat es mit dieser ominösen Draw Methode auf sich?

TGeo ist eine Basisklasse, welche einige grundlegende Dinge festlegt, nämlich, daß sie einen Namen, eine Position und eine Farbe hat sowie die Stiftfarbe und Position sichern und wiederherstellen kann, mehr aber auch nicht.
Die Definition von

Code: Alles auswählen

procedure Draw(Canvas : TCanvas); virtual; abstract; 
besagt, daß von TGeo abgeleitete Klassen diese Methode zwingend implementieren müssen.

Wenn man eine Variable vom Typ TGeo definiert und myGeo := TGeo.Create aufruft, bekommt man vom Compiler eine Warnung, daß eine Klasse mit abstrakten Methoden erzeugt wird. Wird diese abstrakte Methode dann tatsächlich aufgerufen, dann kommt es zu einem Laufzeitfehler.

Man könnte diese Methode auch ohne das Schlüsselwort abstract deklarieren. In dem Fall muß die Methode aber auch schon in der Basisklasse implementiert werden.
Dabei kann es eine leere Methode sein.
Man kann also sagen, wenn Methoden dafür vorgesehen sind, von abgeleiteten Klassen überschrieben zu werden, dann werden sie mit virtual definiert.
Häufig werden virtuelle Methoden sowohl in der Basisklasse als auch in den abgeleiteten Klassen verwendet und überschrieben. Ein Beispiel dafür folgt später bei den Defaulteinstellungen.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Beitrag von corpsman »

Und schon wieder muss ich rummäckeln :)

Code: Alles auswählen

   procedure Draw(Canvas : TCanvas); virtual; abstract;
Leitet man das Object ab und ruft Draw auf, dann bekommt man einen Abstrakten Fehler.
Ich persöhnlich finde diese "Variante" besser, weil die Fehlermeldung deutlicher ist.

Code: Alles auswählen

   procedure Draw(Canvas : TCanvas); virtual; 
..   
implementation

procedure TGeo.Draw(Canvas : TCanvas); 
begin
  raise exception.create('Error: ' + Classname+'.draw not implemented.');
end;
--
Just try it

wp_xyz
Beiträge: 4890
Registriert: Fr 8. Apr 2011, 09:01

Re: Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Beitrag von wp_xyz »

corpsman hat geschrieben:
Mi 26. Okt 2022, 19:40
Ich persöhnlich finde diese "Variante" besser, weil die Fehlermeldung deutlicher ist.
Ich nicht. Denn bei "abstract" kann der Compiler schon vorher eine Warnung ausgeben. Zugegeben, die Fehlermeldung ist für den Einsteiger absolut unverständlich, aber wenn man das einmal mitgemacht hat, weiß man, worum es geht...
Zuletzt geändert von wp_xyz am Fr 28. Okt 2022, 17:45, insgesamt 1-mal geändert.

PascalDragon
Beiträge: 832
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Beitrag von PascalDragon »

Außerdem kann man durch Verwendung von {$warn constructing_abstract error} dafür sorgen, dass der Compiler einen Fehler generiert, wenn du eine Klasse erzeugst, die eine abstrakte Methode enthält (außer man macht dies über eine Klassenvariable):

Code: Alles auswählen

program tabstract;

{$mode objfpc}{$H+}
{$warn constructing_abstract error}

type
  TTest = class
    procedure Test; virtual; abstract;
  end;

  TTest2 = class(TTest)
  end;

var
  t: TTest;
begin
  t := TTest.Create;
  t := TTest2.Create;
end.
FPC Compiler Entwickler

pluto
Lazarusforum e. V.
Beiträge: 7179
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Beitrag von pluto »

Außerdem kann man durch Verwendung von {$warn constructing_abstract error} dafür sorgen, dass der Compiler einen Fehler generiert, wenn du eine Klasse erzeugst, die eine abstrakte Methode enthält (außer man macht dies über eine Klassenvariable):
Warum macht der Kompiler das nicht Automatisch von sich aus?
MFG
Michael Springwald

PascalDragon
Beiträge: 832
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Schritt für Schritt zur klassenbasierten Programmierung - Teil 3

Beitrag von PascalDragon »

pluto hat geschrieben:
Sa 29. Okt 2022, 20:04
Außerdem kann man durch Verwendung von {$warn constructing_abstract error} dafür sorgen, dass der Compiler einen Fehler generiert, wenn du eine Klasse erzeugst, die eine abstrakte Methode enthält (außer man macht dies über eine Klassenvariable):
Warum macht der Kompiler das nicht Automatisch von sich aus?
Rückwärts- und Delphikompatibilität.
FPC Compiler Entwickler

Antworten