Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Vererbung: Wie rufe ich Klasse.Parent.Methode auf? (https://www.delphipraxis.net/182432-vererbung-wie-rufe-ich-klasse-parent-methode-auf.html)

MaBuSE 23. Okt 2014 14:09

Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Hallo,
ich habe ein Problem mit einem Methodenaufruf.

Folgendes Szenario:
Delphi-Quellcode:
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  TMutter = class(TObject)
  public
    procedure a(x: Boolean); virtual;
    procedure b; virtual;
  end;

  TKind = class(TMutter)
  public
    procedure a(x: Boolean); override;
    procedure b; override;
  end;


procedure Log(s:string);

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure Log(s:string);
begin
  Form1.Memo1.Lines.Add(s);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Mutter: TMutter;
  Kind: TKind;
begin
  Memo1.Lines.Clear;

  Log('');
  Log('Mutter beinhaltet Mutter');
  Mutter := TMutter.Create;
  Mutter.a(True); // TMutter.a wird aufgerufen
  Mutter.Free;

  Log('');
  Log('Kind beinhaltet Kind');
  Kind := TKind.Create; // TKind.a wird aufgerufen
  Kind.a(True);
  Kind.Free;

  Log('');
  Log('Mutter beinhaltet Kind');
  Mutter := TKind.Create;
  Mutter.a(False); // TKind.a wird aufgerufen
  Mutter.Free;

end;

{ TKind }

procedure TKind.a(x: Boolean);
begin
  Log('TKind.a1');
  inherited;
  Log('TKind.a2');
end;

procedure TKind.b;
begin
  Log('TKind.b1');
  inherited;
  Log('TKind.b2');
end;

{ TMutter }

procedure TMutter.a(x: Boolean);
begin
  Log('TMutter.a1');
  if x then
  begin
    // b aufrufen. (Wenn Self = TKind ist, dann wird TKind.b aufgerufen.)
    b;
  end
  else
  begin
    // Hier sollte immer TMutter.b aufgerufen werden!!!
    b;
  end;
  Log('TMutter.a2');
end;

procedure TMutter.b;
begin
  Log('TMutter.b');
end;

end.

Bei
Delphi-Quellcode:
Log('Mutter beinhaltet Kind');
wird z.B. folgendes Aufgerufen:
  • TKind.a (da in Mutta ja ein TKind steckt)
  • TMutter.a (inherited)
  • TKind.b (in TMutter.a wird das b von TKind aufgerufen, da Methode a virtual oder dynamic)
  • TMutter.a (inherited)
Wie ist es möglich in TMutter.a TMutter.b aufzurufen, obwohl in TMutter ein TKind steckt?

So eine einfache Lösung wie
Delphi-Quellcode:
TMutter(Self).b
funktionieren natürlich nicht. ;-)

Hat jemand eine Idee?

Ich denke imMoment darüber nach mit den Methoden Pointer zu ermitteln und die Methode über die Adresse direkt aufzurufen. Aber das muß auch einfacher gehen.

Danke im Voraus.
MaBuSE

Uwe Raabe 23. Okt 2014 14:19

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Zitat:

Zitat von MaBuSE (Beitrag 1277171)
Wie ist es möglich in TMutter.a TMutter.b aufzurufen, obwohl in TMutter ein TKind steckt?

Eigentlich stimmt hier dein Design nicht mehr. Es mag andere Ansätze geben, aber ich löse das meistens so:

Delphi-Quellcode:
  TMutter = class(TObject)
  protected
    procedure InternalB;
  public
    procedure a(x: Boolean); virtual;
    procedure b; virtual;
  end;

procedure TMutter.a(x: Boolean);
begin
  Log('TMutter.a1');
  if x then
  begin
    // b aufrufen. (Wenn Self = TKind ist, dann wird TKind.b aufgerufen.)
    b;
  end
  else
  begin
    // Hier sollte immer TMutter.b aufgerufen werden!!!
    InternalB;
  end;
  Log('TMutter.a2');
end;

procedure TMutter.InternalB;
begin
  Log('TMutter.b');
end;

procedure TMutter.b;
begin
  InternalB;
end;

ChrisE 23. Okt 2014 14:28

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Hallo Mabuse,

wenn du alle Quellen in der Hand hast (Mutter & Kind) und wir hier nicht über Sinn & Unsinn deiner Forderung reden wollen :-D dann mach
Delphi-Quellcode:
TMutter.b;
OHNE
Delphi-Quellcode:
virtual
und dementsprechend
Delphi-Quellcode:
TKind.b;
ohne
Delphi-Quellcode:
override
. Dein Problem ist ja eben die VMT die eb en genau dafür sorgen soll, dass nur die Methoden aufgerufen werden die auch wirklich zu dem drinne Steckenden gehören. Also ohne VMT arbeiten und die Methoden überdecken und in den Sourcen selber entscheiden was aufgerufen werden soll aufgrund dessen was man ist.

Sollte eigentlich gehen.

Gruß, Chris

zagota 23. Okt 2014 15:04

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Habe den Source mal angepasst. Nicht schön aber sollte gehen...


Zitat:

Zitat von MaBuSE (Beitrag 1277171)
Hallo,
ich habe ein Problem mit einem Methodenaufruf.

Folgendes Szenario:
Delphi-Quellcode:
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  TMutter = class(TObject)
  public
    procedure a(x: Boolean); virtual;
    procedure b; virtual;
  end;

  TKind = class(TMutter)
  public
    procedure a(x: Boolean); override;
    procedure b; override;
  end;

//++++++++++++++++ inserted +++++++
  TB = procedure of object;
//++++++++++++++++


procedure Log(s:string);

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure Log(s:string);
begin
  Form1.Memo1.Lines.Add(s);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Mutter: TMutter;
  Kind: TKind;
begin
  Memo1.Lines.Clear;

  Log('');
  Log('Mutter beinhaltet Mutter');
  Mutter := TMutter.Create;
  Mutter.a(True); // TMutter.a wird aufgerufen
  Mutter.Free;

  Log('');
  Log('Kind beinhaltet Kind');
  Kind := TKind.Create; // TKind.a wird aufgerufen
  Kind.a(True);
  Kind.Free;

  Log('');
  Log('Mutter beinhaltet Kind');
  Mutter := TKind.Create;
  Mutter.a(False); // TKind.a wird aufgerufen
  Mutter.Free;

end;

{ TKind }

procedure TKind.a(x: Boolean);
begin
  Log('TKind.a1');
  inherited;
  Log('TKind.a2');
end;

procedure TKind.b;
begin
  Log('TKind.b1');
  inherited;
  Log('TKind.b2');
end;

{ TMutter }

procedure TMutter.a(x: Boolean);

// +++++++++++
var
  Proc: TB;
// +++++++++++

begin
  Log('TMutter.a1');
  if x then
  begin
    // b aufrufen. (Wenn Self = TKind ist, dann wird TKind.b aufgerufen.)
    b;
  end
  else
  begin
    // Hier sollte immer TMutter.b aufgerufen werden!!!
// +++++++++++
    TMethod(Proc).Code := @TMutter.b;
    TMethod(Proc).Data := Self;
    Proc();
// +++++++++++
//    b;
  end;
  Log('TMutter.a2');
end;

procedure TMutter.b;
begin
  Log('TMutter.b');
end;

end.

MaBuSE


MaBuSE 24. Okt 2014 10:41

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Zitat:

Zitat von zagota (Beitrag 1277191)
Habe den Source mal angepasst. Nicht schön aber sollte gehen...
Delphi-Quellcode:
...
begin
    // Hier sollte immer TMutter.b aufgerufen werden!!!
// +++++++++++
    TMethod(Proc).Code := @TMutter.b;
    TMethod(Proc).Data := Self;
    Proc();
// +++++++++++
//    b;
  end;
  Log('TMutter.a2');
end;
...

Danke für Deine Antwort.
Zitat:

Zitat von MaBuSE (Beitrag 1277171)
Ich denke imMoment darüber nach mit den Methoden Pointer zu ermitteln und die Methode über die Adresse direkt aufzurufen. Aber das muß auch einfacher gehen.

Das war auch meine Idee, aber wie Du schon sagtest: "nicht schön" :stupid:

@Uwe: Stimmt, auf die Idee bin ich gar nicht gekommen. Ich probier das mal aus. Danke.

@Chris: Das geht leider nicht, da ich das alte Verhalten ja durchaus wünsche, wenn (True) übergeben wurde. Außerdem sollte man wenn man es so macht das Schlüsselwort reintreduce verwenden um zu dokumentieren, dass man genau das machen möchte und nicht aus versehen virtual/override vergessen hat.

Danke für die Antworten

sx2008 24. Okt 2014 23:50

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Ich denke du solltest dir mal das Liskovsches Substitutionsprinzip anschauen.
Verkürzt gesagt muss es jederzeit möglich sein an eine Klasse durch eine davon abgeleitete Klasse zu ersetzen.
Delphi-Quellcode:
// der Procedure muss es egal sein ob "myobject" vom Typ TBasisKlasse oder von einer davon abgeleiteten Klasse ist
procedure Machwas(TBasisKlasse: myobject);
begin
Dies ist auch der Grund dafür weshalb man in abgleiteten Klassen keine Properties oder Methoden entfernen kann.
Auch darf man die Sichtbarkeit (z.B. von public auf private) nicht einschränken.
Das Überdecken einer virtuellen Methode mit
Delphi-Quellcode:
reintroduce
ist ein ganz klares Pfui.

Häufig lassen sich Designprobleme lösen indem man virtuelle Methoden nicht public sondern nur protected macht:
Delphi-Quellcode:
TBaseclass = class(TObject)
protected
   procedure Internal_A(x: Boolean); virtual;
public
   procedure A(x: Boolean);
   
...
procedure TBaseclass.A(x: Boolean);
begin
    // code vorher
    // die Basisklasse kann hier sogar entscheiden ob sie Internal_A() überhaupt aufrufen möchte
    Internal_A(x);  // virtuelle Methode aufrufen
    // code danach (Exceptionhandling, Logging, was auch immer)
end;
Diese Vorgehensweise ist auch als template method pattern bekannt.

ChrisE 27. Okt 2014 06:42

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Hallo MaBuSE,

Zitat:

Zitat von MaBuSE (Beitrag 1277285)
@Chris: Das geht leider nicht, da ich das alte Verhalten ja durchaus wünsche, wenn (True) übergeben wurde. Außerdem sollte man wenn man es so macht das Schlüsselwort reintreduce verwenden um zu dokumentieren, dass man genau das machen möchte und nicht aus versehen virtual/override vergessen hat.

Delphi-Quellcode:
reintroduce
bin ich zu 100% bei dir. Der Quelltext funktioniert aber tatsächlich, wenn du es so machst - zumindest bei mir XE5.

Delphi-Quellcode:
  TMutter = class(TObject)
  public
    procedure a(x: Boolean); virtual;
    procedure b;
  end;

  TKind = class(TMutter)
  public
    procedure a(x: Boolean); override;
    procedure b;reintroduce;
  end;

//....


procedure TMutter.a(x: Boolean);
begin
  Log('TMutter.a1');
  if x then
  begin
    // b aufrufen. (Wenn Self = TKind ist, dann wird TKind.b aufgerufen.)
    TKind(Self).b;
  end
  else
  begin
    // Hier sollte immer TMutter.b aufgerufen werden!!!
    TMutter(Self).b;
  end;
  Log('TMutter.a2');
end;
Oder is dir das zu explizit?

Gruß, Chris

MaBuSE 27. Okt 2014 10:01

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Zitat:

Zitat von ChrisE (Beitrag 1277460)
Oder is dir das zu explizit?

Ja :wink:
Ich bin Fan von KISS ("keep it stupid simple" oder "So einfach wie möglich, so kompliziert wie nötig")


Die Lösung mit dem internal_procname finde ich gut.
Sie ist vor allem einfacher zu verstehen, wenn man in ein paar Jahren noch mal über die Zeilen stolpert.

Internal_procname hatte ich schon in den protected Abschnitt gesetzt, bevor ich die Nachricht von sx2008 las.

Ich habe das mit dem internal_procname auch schon früher gemacht, aber aus irgendeinem Grund habe ich mich diesmal in dem Problem festgefahren. Man sieht den Wald vor lauter Bäumen nicht. Normalerweise finde ich die Lösung dann beim Erstellen des Beispielprogramms, diesmal nicht :stupid:
Da ist es gut, wenn man von der DP einen Schups in die richtige Richtung bekommt.
:dp:

Danke für alle Antworten.
MaBuSE

Dejan Vu 27. Okt 2014 12:21

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Zitat:

Zitat von ChrisE (Beitrag 1277460)
Delphi-Quellcode:
...
procedure TMutter.a(x: Boolean);
begin
...
  TKind(Self).b;
...

Schwerer Fehler.
1. Woher soll die Mutter wissen, das es ein Kind gibt?
2. Welche Methode soll aufgerufen werden, wenn es noch eine Ableitung der TMutter (oder von TKind) gibt?
3. Wenn die Mutter wissen muss, das es genau das eine Kind gibt, wozu dann zwei Klassen?

Mach es so, wie Uwe vorgeschlagen hat. Die Mutter ruft entweder explizit die eigene Methode oder aber die virtuelle Methode auf. So ist es OOP-konform und auch sauber.

MaBuSE 27. Okt 2014 13:45

AW: Vererbung: Wie rufe ich Klasse.Parent.Methode auf?
 
Zitat:

Zitat von Dejan Vu (Beitrag 1277502)
Zitat:

Zitat von ChrisE (Beitrag 1277460)
Delphi-Quellcode:
...
procedure TMutter.a(x: Boolean);
begin
...
  TKind(Self).b;
...

Schwerer Fehler.
1. Woher soll die Mutter wissen, das es ein Kind gibt?

Delphi-Quellcode:
...
  if Self is TKind then (Self as TKind).b;
...
Zitat:

Zitat von Dejan Vu (Beitrag 1277502)
2. Welche Methode soll aufgerufen werden, wenn es noch eine Ableitung der TMutter (oder von TKind) gibt?

In diesem Fall würde automatisch die von TMutter aufgerufen.
Da sie ja nicht mit reintreduce überschrieben wurde.

Zitat:

Zitat von Dejan Vu (Beitrag 1277502)
3. Wenn die Mutter wissen muss, das es genau das eine Kind gibt, wozu dann zwei Klassen?

Das ist die Schwachstelle. Dann wird es umständlich.
Delphi-Quellcode:
...
  if Self is TLieblingsKind then
  begin
   (Self as TLieblingsKind).b
  end
  else
  begin
    if Self is TStiefKind then
    begin
      (Self as TStiefKind).b
    end
    else
    begin
      // Mutterschaftstest durchführen.

      // Den Vorteil, den Mütter haben ist,
      // das sie in der Regel wissen, dass es ihre Kinder sind.
    end;
  end;
...
Aber wahrscheinlich gibt es dafür auch einen einfacheren Weg.

Fakt ist, das die Mutter alle Kinder (und Kindes-Kinder), die eine Sonderbehandlung benötigen, kennen muss.
Das icht schlecht.
Das ist bei der internal_proc Lösung nicht der Fall.

Zitat:

Zitat von Dejan Vu (Beitrag 1277502)
Mach es so, wie Uwe vorgeschlagen hat. Die Mutter ruft entweder explizit die eigene Methode oder aber die virtuelle Methode auf. So ist es OOP-konform und auch sauber.

Ich habe es wie Uwe gemacht.
Wenn du meine Texte gelesen hättest, wüsstest Du wie ich darüber denke.


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