Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Eine Frage zum Thema Polymorphie!


Elrond - Do 01.01.09 19:42
Titel: Eine Frage zum Thema Polymorphie!
Hallo und euch allen erstmal ein frohes neues Jahr. Hier ist mein Problem. Seit einigen Monaten arbeite ich mit Delphi 5. Abgesehen von den GUI-Komponenten habe ich bisher prozedural programmiert. Jetzt habe ich mit OOP begonnen. Dabei möchte ich mit Objekten arbeiten, deren Typ bei der Compilation noch nicht bekannt ist und erst während der Laufzeit festgelegt wird.

Ich habe nun zwei allgemeine Variablen definiert.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
var
  p1,p2: TObject;

Desweiteren gibt es zwei Klassen:

TObjekt1=Class(TObject)
  private
   sig:string;
  public
   procedure init; virtual;abstract;
   ...
end;

TObjekt2 = Class(TObjekt1)
  private
   re,im:Extended;
  public
   procedure init(re_,im_:Extended); overload;
   ...
end;


In Abhängigkeit von gewissen Eingaben mache ich dann folgendes:


Delphi-Quelltext
1:
2:
3:
4:
p1:=TObjekt2.Create; (Klappt! Compiler sagt: Erstelle Instanz von TObjekt2 mit abstrakten Methoden)
p2:=TObjekt2.Create; (Hier ebenso)
p1.init(r1,r2); (...und hier steigt der Compiler aus mit: "undefinierter Bezeichner init")
p2.Init(r3,r4);


r1,r2,r3,r4 sind Extended-Variablen, es wurde ein Objekt vom Typ TObjekt2 erzeugt, der Pointer wurde p1 zugewiesen, also verweist p1 auf ein Objekt vom Typ TObjekt2 und müsste demnach auch die Methode init kennen.
Übrigens, wenn ich p1 nicht als Variable vom Typ TObject sondern vom Typ TObjekt2 deklariere, dann funktioniert es, hilft mir aber nicht weiter. Ich möchte während der Laufzeit verschiedene Typen von Objekten instanzieren und dann im weiteren jeweils über p1 und p2 ansprechen.

Wo liegt der Fehler?

Schon mal Danke im voraus.

Elrond.

Moderiert von user profile iconGausi: Delphi-Tags hinzugefügt


Elrond - Do 01.01.09 20:57

Falls niemand den Fehler entdeckt, wäre ich auch schon damit zufrieden, wenn mir jemand einen Tipp geben könnte, um das zu erreichen, was ich möchte.

Allgemein: Ich möchte verschiedene Klassen C1, C2,... definieren, die jeweils gleichnamige Methoden besitzen, zum Beispiel M. Dann möchte ich p.M aufrufen und vorher während der Laufzeit entscheiden, ob p auf ein Objekt vom Typ C1, C2 und so weiter zeigt.

Thx.

Elrond.


UGrohne - Do 01.01.09 20:59

Die Typ-Deklaration Deiner Variable muss schon Kenntnisse über die Methoden haben, die Du aufrufen möchtest und genau diese Kenntnis besitzt die Klasse object natürlich nicht. In Deinem Fall würde es aufgrund der Vererbung von TObjekt1 nach TObjekt2 ausreichen, wenn Du Deine Variablen als TObjekt1 deklarierst.

Eine bessere Alternative stellt allerdings die Verwendung von Interfaces dar, die Dir den Bauplan aller Klassen vereinheitlicht. Deine Variablen sind dann vom Typ des Interfaces und kennen die Methode Init:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
var
  p1,p2: IObject;
IObject = interface
  procedure init;
end;

TObjekt1=Class(TObject, IObject)
  private
   sig:string;
  public
   procedure init; virtual;abstract;
   ...
end;

TObjekt2 = Class(TObjekt1, IObject)
  private
   re,im:Extended;
  public
   procedure init(re_,im_:Extended); overload;
   ...
end;


jaenicke - Do 01.01.09 21:24

Nur dass im Interface bzw. der Oberklasse die Methode init auch mitsamt der Parameter definiert werden muss. Denn sonst kennt der Compiler die Parameter ja nicht. ;-)


Elrond - Do 01.01.09 21:29

user profile iconUGrohne hat folgendes geschrieben Zum zitierten Posting springen:
Die Typ-Deklaration Deiner Variable muss schon Kenntnisse über die Methoden haben, die Du aufrufen möchtest und genau diese Kenntnis besitzt die Klasse object natürlich nicht. In Deinem Fall würde es aufgrund der Vererbung von TObjekt1 nach TObjekt2 ausreichen, wenn Du Deine Variablen als TObjekt1 deklarierst.

Eine bessere Alternative stellt allerdings die Verwendung von Interfaces dar, die Dir den Bauplan aller Klassen vereinheitlicht. Deine Variablen sind dann vom Typ des Interfaces und kennen die Methode Init:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
var
  p1,p2: IObject;
IObject = interface
  procedure init;
end;

TObjekt1=Class(TObject, IObject)
  private
   sig:string;
  public
   procedure init; virtual;abstract;
   ...
end;

TObjekt2 = Class(TObjekt1, IObject)
  private
   re,im:Extended;
  public
   procedure init(re_,im_:Extended); overload;
   ...
end;


Hallo Uwe, danke für Deine Antwort. Also ich habe jetzt mal wie vorgeschlagen p als TObjekt1 deklariert, bekomme jetzt aber die Fehlermeldung "Zu viele Parameter" beim Aufruf von p.init(r1,r2). Irgendwie habe ich den Eindruck, dass die Variable p weiterhin auf ein Objekt vom Typ Objekt1 zeigt, obwohl der Compiler bestätigt, dass eine Instanz von Objekt2 erzeugt und der Variable p zugewiesen wurde.

Elrond


Elrond - Do 01.01.09 21:33

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nur dass im Interface bzw. der Oberklasse die Methode init auch mitsamt der Parameter definiert werden muss. Denn sonst kennt der Compiler die Parameter ja nicht. ;-)


...und eben das ist das Problem. Nehmen wir einmal an, dass Init als Methode von TObjekt2 zwei Parameter hat, als Methode von TObjekt3 7 Parameter und als Methode von TObjekt4 Null Parameter. Ich möchte zur Laufzeit entscheiden, ob p auf ein Objekt vom Typ TObjekt2, TObjekt3 oder TObjekt4 zeigt und init dann mit der entsprechenden Parameterzahl aufrufen.

Elrond


jaenicke - Do 01.01.09 21:45

Dann kannst du ein array of const als einen einzelnen Parameter nehmen, aufrufen kannst du das dann wie den Format Befehl von Delphi.


Elrond - Do 01.01.09 21:59

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Dann kannst du ein array of const als einen einzelnen Parameter nehmen, aufrufen kannst du das dann wie den Format Befehl von Delphi.


Das Problem ist, dass p auch nach der Zuweisung p:=TObjekt2.Create immer noch auf ein Objekt vom Typ TObjekt1 zu zeigen scheint und nicht auf ein Objekt vom Typ TObjekt2, obwohl gerade so ein Objekt laut Compiler erzeugt wurde. Irgendwie habe ich den Eindruck, als ob p statisch wäre, so wie im alten Objektmodell (type = object) und nicht dynamisch wie im neuen Objektmodell (type=class) eigentlich der Fall sein sollte. Bei mir macht sich so langsam der Frust breit... :(

Elrond


Elrond - Do 01.01.09 22:17

user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Dann kannst du ein array of const als einen einzelnen Parameter nehmen, aufrufen kannst du das dann wie den Format Befehl von Delphi.


Das Problem ist, dass p auch nach der Zuweisung p:=TObjekt2.Create immer noch auf ein Objekt vom Typ TObjekt1 zu zeigen scheint und nicht auf ein Objekt vom Typ TObjekt2, obwohl gerade so ein Objekt laut Compiler erzeugt wurde. Irgendwie habe ich den Eindruck, als ob p statisch wäre, so wie im alten Objektmodell (type = object) und nicht dynamisch wie im neuen Objektmodell (type=class) eigentlich der Fall sein sollte. Bei mir macht sich so langsam der Frust breit... :(

Elrond


Mein Verdacht hat sich bestätigt. Ich habe jetzt init überall mit identischen Parametern deklariert, so dass der Compiler das schluckt. Es gibt nun wie erwartet einen Laufzeitfehler, weil das Programm versucht, die abstrakte Methode init aus TObjekt1 aufzurufen und nicht die konkrete Methode init aus TObjekt2. Wie kann das sein? p:=TObjekt2.Create erzeugt ein Objekt vom Typ TObjekt2. :roll:

Elrond.


jaenicke - Do 01.01.09 22:18

Das dürfte eigentlich nicht passieren, wie sieht denn der Code jetzt aus? Hast du die methode der Oberklasse mit override korrekt überschrieben?
Overload erzeugt nur eine weitere gleichnamige Methode, hat mit der in der Oberklasse aber nix zu tun.


Elrond - Do 01.01.09 22:30

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das dürfte eigentlich nicht passieren, wie sieht denn der Code jetzt aus? Hast du die methode der Oberklasse mit override korrekt überschrieben?
Overload erzeugt nur eine weitere gleichnamige Methode, hat mit der in der Oberklasse aber nix zu tun.


Ja, ich habe override verwendet. Deklariere ich p als TObjekt1 => Laufzeitfehler, deklariere ich p als TObjekt2 läuft alles... :?

Elrond.


jaenicke - Do 01.01.09 22:54

Ich sagte ja, poste doch mal den Code. Bei mir jedenfalls funktioniert das alles wie es soll: :nixweiss:

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:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
type
  TObjekt1=Class(TObject)
  private
    sig:string;
  public
    procedure init(re_,im_: Extended); virtualabstract;
    procedure init2(data: array of const); virtualabstract;
  end;

  TObjekt2 = Class(TObjekt1)
  private
    re,im:Extended;
  public
    procedure init(re_,im_:Extended); override;
    procedure init2(data: array of const); override;
  end;

  TForm118 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form118: TForm118;

implementation

{$R *.dfm}

procedure TForm118.Button1Click(Sender: TObject);
var
  p1: TObjekt1;
begin
  p1 := TObjekt2.Create;
  p1.init(12.322.4);
  ShowMessage(p1.sig);
  p1.init2([112.3122.4]);
  ShowMessage(p1.sig);
end;

{ TObjekt2 }

procedure TObjekt2.init(re_, im_: Extended);
begin
  re := re_;
  im := im_;
  sig := FloatToStr(re) + ' - ' + FloatToStr(im);
end;

procedure TObjekt2.init2(data: array of const);
begin
  re := data[0].VExtended^;
  im := data[1].VExtended^;
  sig := Format('Die Zahlen: %.2f und %.2f', data);
end;

end.


Elrond - Do 01.01.09 23:04

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Ich sagte ja, poste doch mal den Code. Bei mir jedenfalls funktioniert das alles wie es soll: :nixweiss:

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:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
type
  TObjekt1=Class(TObject)
  private
    sig:string;
  public
    procedure init(re_,im_: Extended); virtualabstract;
    procedure init2(data: array of const); virtualabstract;
  end;

  TObjekt2 = Class(TObjekt1)
  private
    re,im:Extended;
  public
    procedure init(re_,im_:Extended); override;
    procedure init2(data: array of const); override;
  end;

  TForm118 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form118: TForm118;

implementation

{$R *.dfm}

procedure TForm118.Button1Click(Sender: TObject);
var
  p1: TObjekt1;
begin
  p1 := TObjekt2.Create;
  p1.init(12.322.4);
  ShowMessage(p1.sig);
  p1.init2([112.3122.4]);
  ShowMessage(p1.sig);
end;

{ TObjekt2 }

procedure TObjekt2.init(re_, im_: Extended);
begin
  re := re_;
  im := im_;
  sig := FloatToStr(re) + ' - ' + FloatToStr(im);
end;

procedure TObjekt2.init2(data: array of const);
begin
  re := data[0].VExtended^;
  im := data[1].VExtended^;
  sig := Format('Die Zahlen: %.2f und %.2f', data);
end;

end.


Danke für das Programm. Das mit dem array of const habe ich noch nicht so ganz verstanden. Angenommen ich möchte an die Methode init2 ein Objekt vom Typ TObjekt2 übergeben, wie mache ich das?

Elrond.


Kha - Do 01.01.09 23:08

user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Das mit dem array of const habe ich noch nicht so ganz verstanden.
Overrides mit "verschiedenen" Parametern sind auch alles andere als normal, das sieht stark nach einem Designfehler aus. Vielleicht finden wir ja ein besseres Design, wenn du ein reales Beispiel nennen kannst.


jaenicke - Do 01.01.09 23:24

user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Das mit dem array of const habe ich noch nicht so ganz verstanden.
http://rvelthuis.de/articles/articles-openarr.html
Das hilft vielleicht beim Verständnis.

user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Angenommen ich möchte an die Methode init2 ein Objekt vom Typ TObjekt2 übergeben, wie mache ich das?
Du kannst ja die gemeinsame Elternklasse als Typ des Parameters nehmen, meinst du das?

Oder willst du wirklich konkret TObjekt2 immer übergeben? Das ginge via Forwarddeklaration:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
  TObjekt1 = class(TObject);
  TObjekt2 = Class(TObjekt1);

  TObjekt1 = class(TObject)
  public
    procedure init(a: TObjekt2); virtualabstract;
  end;

  TObjekt2 = Class(TObjekt1)
  public
    procedure init(a: TObjekt2); override;
  end;
Aber ich nehme an das meintest du nicht.

Ansonsten kann ich mich nur user profile iconKha anschließen:
Ohne zu wissen was du vor hast können wir dir kein besseres Design vorschlagen. Und du selbst scheinst ja nicht so genau zu wissen was eigentlich wie zusammenhängen soll. Bzw. das nicht mit Delphi in Einklang bringen zu können. ;-)


Elrond - Do 01.01.09 23:27

user profile iconKha hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Das mit dem array of const habe ich noch nicht so ganz verstanden.
Overrides mit "verschiedenen" Parametern sind auch alles andere als normal, das sieht stark nach einem Designfehler aus. Vielleicht finden wir ja ein besseres Design, wenn du ein reales Beispiel nennen kannst.


Ok. Das Ziel hatte ich ja schon mal weiter oben skizziert. Angenommen, ich habe verschiedene Objektklassen TVektor, TKomplexezahl und so weiter. Alle diese Klassen haben Methoden mit unterschiedlichen Parametern aber identischen Namen. Zum Beispiel eine Methode Addiere.

Einmal addiere ich also zwei Vektoren, ein anderes mal zwei komplexe Zahlen. Ich möchte nun an einer zentralen Stelle (zum Beispiel hier: procedure TForm1.AdditionClick(Sender: TObject) eine Addition durchführen: p1.Addiere(p2).

Das soll für alle Typen von addierbaren Objekten gelten. D.h. einmal sind p1, p2 Instanzen der Klasse TVektor, ein andermal Instanzen der Klasse TKomplexeZahl. Was jeweils gilt, möchte ich während der Laufzeit festlegen. Das hatte ich mir so in der Art p1:=TVektor.Create oder p1:=TKomplexeZahl.Create vorgestellt.

Jetzt ergeben sich natürlich Probleme.

1) Von welchem Typ sollten die Variablen p1,p2 bei der Deklaration sein?

2) Was mache ich mit den unterschiedlichen Parametern?

Wenn ich später weitere addierbare Objekte hinzufügen möchte (zum Beispiel Brüche oder ganze Zahlen), müssten die zentralen Stellen des Programms nicht geändert werden, denn jeder neue Objekttyp bringt ja seine ganz eigenen Methoden zum Addieren mit, benötigt aber unterschiedliche Parameter, teilweise auch unterschiedliche Parameterzahlen (2 ganze Zahlen = 2 Parameter, zwei Brüche = 4 Parameter (zwei Zähler, zwei Nenner)). Leider ist das wohl doch nicht so einfach.

Ich bin dankbar für jede gute Idee.

Elrond.


jaenicke - Do 01.01.09 23:42

Das wiederum ist ein relativ einfaches Problem. Du brauchst doch gar nicht verschieden viele Parameter. Schließlich wird dein Bruch z.B. doch in einem Objekt gekapselt. Und an die Methode übergibst du den Bruch als ganzes Objekt, als einen Parameter. ;-)

Die andere Möglichkeit wäre natürlich theoretisch die über ein Array als Parameter, sei es ein normales open array als Parameter oder ein variant open array parameter (array of const).
Das ist in diesem Fall aber keine gute Wahl, aus den von dir selbst genannten Gründen.

// EDIT:
Ach ja: neuere Versionen von Delphi (ab Delphi 2006) kennen auch Operator Overloading:

Delphi-Quelltext
1:
2:
3:
4:
  TBruch = record
    Zaehler, Nenner: Integer;
    class operator Add(a, b: TBruch): TBruch;
  end;
Dann kannst du einfach schreiben:

Delphi-Quelltext
1:
2:
3:
4:
5:
var
  test1, test2: TBruch;
begin
  ...
  test1 := test1 + test2;
Mit Delphi für Win32 geht das nur mit Records, mit Delphi Prism unter .NET geht das auch mit Klassen.

Und mit C# geht das natürlich auch. ;-)

// EDIT2:
Du könntest den Record ja auch in eine Klasse als Eigenschaft einbetten, dann hättest du beides, wenn dir die Funktionalität eines Records nicht ausreicht.


Kha - Fr 02.01.09 00:48

Ja, hier bietet sich dann wirklich ein Interface an:

Delphi-Quelltext
1:
2:
3:
4:
5:
type INumeric = interface
  function Add(aOther : INumeric) : INumeric;
  function Sub(aOther : INumeric) : INumeric;
  function Negate : INumeric;
  [...]

In den Implementationen musst du dann nur noch überprüfen, ob Self und der Parameter wirklich vom gleichen(/kompatiblen) Typ sind.
Man könnte auch mit einer Basisklasse ein wenig Arbeit abnehmen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
type NumericBase = class(TObject, INumeric)
  function Add(aOther : INumeric) : INumeric; virtualabstract;
  function Sub(aOther : INumeric) : INumeric; virtual;
  function Negate : INumeric; virtualabstract;
  [...]

function NumericBase.Sub(aOther : INumeric);
begin
  Result := Add(aOther.Negate);
end;


Elrond - Fr 02.01.09 01:29

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das wiederum ist ein relativ einfaches Problem. Du brauchst doch gar nicht verschieden viele Parameter. Schließlich wird dein Bruch z.B. doch in einem Objekt gekapselt. Und an die Methode übergibst du den Bruch als ganzes Objekt, als einen Parameter. ;-)


Bei Init habe ich sehr wohl verschiedene Parameterzahlen. Bei einem Objekt vom Typ ganze Zahl ist es ein Integer-Wert, bei einem Bruch sind es zwei Integer-Werte. Den Bruch kann ich nicht übergeben, denn dessen Werte sollen ja erst initialisiert werden.

Und auch das Kapseln führt zum Anfangsproblem zurück, denn einmal habe ich so etwas wie

procedure Add(s:TKomplex);

in der Klassendefinition und ein anderes mal

procedure Add(s:TVektor);

und wenn ich dann p1.Add(p2) schreibe dann schimpft der Compiler entweder, dass p2 nicht vom Typ TKomplex ist oder aber, dass p2 nicht vom Typ TVektor ist, je nachdem wie p2 definiert ist. Deswegen hatte ich mir ja überlegt, beide Klassen von einer gemeinsamen Basisklasse abzuleiten. Aber da wird dann jeweils die abstrakte Methode aufgerufen und ein Laufzeitfehler tritt auf.

Elrond


jaenicke - Fr 02.01.09 01:35

user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Bei Init habe ich sehr wohl verschiedene Parameterzahlen. Bei einem Objekt vom Typ ganze Zahl ist es ein Integer-Wert, bei einem Bruch sind es zwei Integer-Werte. Den Bruch kann ich nicht übergeben, denn dessen Werte sollen ja erst initialisiert werden.
Aber bei der Initialisierung ist doch keine allgemeingültige Lösung nötig. :gruebel:
Da musst du doch ohnehin das Objekt gezielt mit einem bestimmten Typ erstellen. Und dann kannst du es dabei auch direkt initialisieren. Immer wenn du die mit den Werten direkt arbeitest, kannst du doch ohnehin nicht mehr allgemein bleiben.

Und bei mir habe ich ja auch das Array benutzt, solche Konstruktionen wären theoretisch ja auch denkbar.

user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Deswegen hatte ich mir ja überlegt, beide Klassen von einer gemeinsamen Basisklasse abzuleiten. Aber da wird dann jeweils die abstrakte Methode aufgerufen und ein Laufzeitfehler tritt auf.
Aber nur, wenn du nicht die Oberklasse als Parameter benutzt. Wie es richtig geht, habe ich doch in dem Beispiel gezeigt. Du hast immer einen Parameter vom Typ der Oberklasse als Parameter. Bzw. es wird eine solche Methode von allen implementiert.

Mit einem Interface geht das ja. Dann brauchst du nicht einmal von einer Oberklasse ableiten, wie ich es im Beispiel gemacht hatte.

Ich glaube ich muss einmal ein richtiges Beispiel machen, das scheint so nicht wirklich rüberzukommen wie das funktioniert. :nixweiss:


Elrond - Fr 02.01.09 01:45

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconElrond hat folgendes geschrieben Zum zitierten Posting springen:
Bei Init habe ich sehr wohl verschiedene Parameterzahlen. Bei einem Objekt vom Typ ganze Zahl ist es ein Integer-Wert, bei einem Bruch sind es zwei Integer-Werte. Den Bruch kann ich nicht übergeben, denn dessen Werte sollen ja erst initialisiert werden.
Aber bei der Initialisierung ist doch keine allgemeingültige Lösung nötig. :gruebel:
Da musst du doch ohnehin das Objekt gezielt mit einem bestimmten Typ erstellen. Und dann kannst du es dabei auch direkt initialisieren. Immer wenn du die mit den Werten direkt arbeitest, kannst du doch ohnehin nicht mehr allgemein bleiben.


Da hast du natürlich recht. Aber es wäre halt allgemeingültig schöner gewesen. Was mich primär interessiert ist, wie p einmal ein Objekt vom Typ TVektor repräsentieren kann und einmal ein Objekt vom Typ TKomplex, so dass zentral wirklich nur einmal p1.Addiere(p2) auftaucht. Von welchem Typ sollte die Variable p dann sein?


jaenicke - Fr 02.01.09 01:49

Entweder muss die Variable den Typ der Oberklasse oder des Interfaces haben, je nachdem wie du es implementierst. Und natürlich musst du auch override und nicht overload benutzen, sonst überschreibst du die abstrakte Klasse nicht, und dann gibt es natürlich einen Fehler.


Elrond - Fr 02.01.09 02:06

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Entweder muss die Variable den Typ der Oberklasse oder des Interfaces haben, je nachdem wie du es implementierst. Und natürlich musst du auch override und nicht overload benutzen, sonst überschreibst du die abstrakte Klasse nicht, und dann gibt es natürlich einen Fehler.


Ok. Dann nehmen wir mal dieses Beispiel:

TMatheObjekt=Class(TObject)
private
sig:string;
public
procedure Add(s:TMatheObjekt); virtual;abstract;
...
end;

TKomplex = Class(TMatheObjekt)
private
re,im:Extended;
public
procedure Add(s:TKomplex); override;
end;

Das mag der Compiler nicht: [Fehler] Unit1.pas(93): Deklaration von 'Add' unterscheidet sich von vorheriger Deklaration.

Wenn ich jedoch in der abgeleiteten Klasse

procedure Add(s:TMatheObjekt); override;

schreibe, dann kennt die überschriebene Prozedur Add bei der Implementierung die Variablen re und im nicht mehr.


jaenicke - Fr 02.01.09 02:28

In der Implementierung musst du die übergebene Variable nach TKomplex casten. ;-)

Delphi-Quelltext
1:
2:
3:
4:
5:
if s is TKomplex then
begin
  re := re + TKomplex(s).re;
  im := im + TKomplex(s).im;
end;
is prüft, ob das übergebene Objekt den richtigen Typ hat, und danach kannst du dann hart casten ohne weitere Prüfung.


alzaimar - Fr 02.01.09 10:16

Also ich würde die Initialisierung mit einem Konstruktor machen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
Type
  TObject1 = Class (TBase)
    Constructor Create (einParameter : TBase);
  End;

  TObject2 = Class (TBase)
    Constructor Create (einParameter, nochEinParameter : TBase);
  End;
...

Wenn Du einen einheitlichen 'Grundkonstruktor' willst, dann spendierst Du jeder Klasse ein parameterloses Create, das von jedem parametrierten 'Create' aufgerufen wird. So mach ich das jedenfalls mit solchen Klassenfamilien.


Elrond - Fr 02.01.09 13:20

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
In der Implementierung musst du die übergebene Variable nach TKomplex casten. ;-)

Delphi-Quelltext
1:
2:
3:
4:
5:
if s is TKomplex then
begin
  re := re + TKomplex(s).re;
  im := im + TKomplex(s).im;
end;
is prüft, ob das übergebene Objekt den richtigen Typ hat, und danach kannst du dann hart casten ohne weitere Prüfung.


Das war's. Alles läuft wie gewünscht. Herzlichen Dank. :zustimm: :dance: