![]() |
Meine Probleme mit Delphi-OOP ...
Hallo alle miteinander,
zur Historie Ich begann in der Schule Delphi zu lernen, wie man das eben so macht; tippeln, verstehen, freuen :-D Dann habe ich C, Java, C++ im Zuge des Studiums gelernt und kann jetzt OOP *freu* :-D Nun kehre ich zu Delphi zurueck und gerate ganz schoen ins Straucheln ... :cry: Meine Probleme Ich habe also in C++ intensiver Klassengestaltung gelernt; ebenso virtuelle und abstrakte Methoden. Aufgrund dessen kollidiere ich in Delphi mit den Worten >Verdecken/Verbergen< und >Ueberschreiben<. Darueber hinaus programmiere ich ja auch in der Delphi IDE und diese erzeugt ja schonmal vorab eine ganze Menge Quelltext. Umso mehr knallt's dann, wenn ich versuche ein halbwegs vernuenftiges Klassenkonzept zu realisieren, ohne komplett in der vorgefertigten Kiste "herumzupfuschen" ... Jetzt ganz genau 1.) Was ist der Unterschied zwischen Verbergen/Verdecken und Ueberschreiben? Ich habe widerspruechliche Dinge gelesen, nach welchen die verdeckten Methoden wieder mit inherited sichtbar gemacht werden koennen ... 2.) Ich habe Zwei Delphi-Formen, wobei ich die erste mit einem eigenen Konstruktor Create(Eigentuemer: TComponent; Kanalnummer: Integer) erzeuge und die Meldung bekomme: [Warnung] Unit2.pas(25): Methode 'Create' verbirgt virtuelle Methode vom Basistyp 'TCustomForm' Dabei hat meiner aber eine andere Signatur als der originaere --> Create(AOwner: TComponent) Was soll ich tun (Bitte nicht nur sagen: reintroduce), ich will ihn ja eigentlich ueberladen, aber mit overload verschwindet die Warnung auch nicht, und Warnungen ignoriere ich nicht. 3.) Ist es ueberhaupt moeglich, ohne den von Delphi automatisch erzeugten Quelltext umzubauen, vernuenftig und streng objektorientiert zu programmieren? Es geht dabei primaer um GUIs. Ich habe zum Beispiel erstmal die Klassen umbenannt, Zugriffspezifizierer gesetzt, globale Variablen fuer die Forms entfernt (>Form1: TForm1<), Konstruktoren geschrieben, automatische Erzeugungen (FormCreate) entfernt ... Sieht jetzt so aus:
Delphi-Quellcode:
program Gehoerbildung;
uses Forms, Unit1 in 'Unit1.pas' {Hauptfenster}, Unit2 in 'Unit2.pas' {GeraeteDialog}; {$R *.RES} var Hauptfenster: THauptfenster; begin Hauptfenster := THauptfenster.Create(nil); Application.Initialize; Application.CreateForm(THauptfenster, Hauptfenster); Application.Run; end.
Delphi-Quellcode:
--> Hier fehlen die globalen Variablen ...
unit Unit1;
interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Buttons, MidiOut, Unit2; type TTon = array [1..5] of Integer; THauptfenster = class(TForm) published AbsoluterTonCheckBox: TCheckBox; ZweiToeneCheckBox: TCheckBox; DreiToeneCheckBox: TCheckBox; VierklangCheckBox: TCheckBox; FuenfklangCheckBox: TCheckBox; HoheLageCheckBox: TCheckBox; MittlereLageCheckBox: TCheckBox; TiefeLageCheckBox: TCheckBox; GenugButton: TButton; MIDIDialogButton: TButton; WiederholungButton: TBitBtn; NeuerVersuchButton: TBitBtn; AufloesungButton: TBitBtn; CrashButton: TBitBtn; GehoerbildungLabel: TLabel; KleinC: TLabel; GrossC: TLabel; CEins: TLabel; CZwei: TLabel; CDrei: TLabel; Trennlinie1: TShape; Trennlinie2: TShape; Shape1: TShape; private FToene: TTon; FGeraeteDialog: TGeraeteDialog; FMidiOut: TMidiOutput; property GeraeteDialog: TGeraeteDialog read FGeraeteDialog write FGeraeteDialog; published procedure NeuerVersuchButtonClick(Sender: TObject); procedure WiederholungButtonClick(Sender: TObject); procedure AufloesungButtonClick(Sender: TObject); procedure CrashButtonClick(Sender: TObject); procedure GenugButtonClick(Sender: TObject); procedure MIDIDialogButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure FormDestroy(Sender: TObject); procedure ErzeugeKlang(AnzahlToene: Integer); procedure BestimmeTiefstUndHoechstton(var Tiefster: Integer; var Hoechster: Integer); public function GetTon(Stelle: Integer): Integer; procedure SetTon(Stelle, NeuerTon: Integer); property Toene[Stelle: Integer]: Integer read GetTon write SetTon; property MidiOut: TMidiOutput read FMidiOut write FMidiOut; end; implementation 4.) Wie verwendet man die Zugriffsspezifizierer (>Sichtbarkeitsattribute<) richtig? Musste zwangslaeufig das Meiste in published-Bloecke tun, dass das Programm ueberhaupt uebersetzt wird bzw. nicht abbricht aufgrund fehlender Klassenbestandteile. Aber das ist doch doof - steht doch total im Widerspruch zu Datenkapselung. Nachtrag Ich habe mich hier schon durch ein Paar Themen gelesen (mittels der Suche), ebenso durch mehrere Tutorien. Doch habe ich's immer noch nicht ganz begriffen. Bitte versteht also, wenn ich keine weiteren Verweise darauf suche, sondern es noch mal mit euren Worten erklaert bekommen moechte. Das bezieht sich insbesondere auf die Zugriffsspezifizierer und Ueberladen etc. . Bis zur Hilfe, Ri |
AW: Meine Probleme mit Delphi-OOP ...
Das Überschreiben und Verdecken sollte es aber auch im C++ geben. (auch wenn dort die Syntax vielleicht etwas anders ist)
Inherited hat damit erstmal grundsätzlich garnichts zu tun. Der Aufruf von inherited besagt nur, daß man an dieser Stelle die Methode eines Vorfahren und nicht eine "Eigene" aufrufen will und das geht sowohl bei überdeckten, als auch bei überschriebenen Methoden.
Delphi-Quellcode:
type
TMyClassA = class procedure A; overload; // überladen procedure A(i: Integer); overload; procedure X; procedure Y; virtual; // mit oder ohne abstract procedure Z; virtual; // mit oder ohne abstract end; TMyClassB = class(TMyClassA) procedure X; // verdeckt procedure Y; // verdeckt procedure Z; override; // überschrieben end; var C: TMyClassA; D: TMyClassA; E: TMyClassB; C := TMyClassB.Create; C.Y; // TMyClassA.Y wird ausgeführt, da die Methode nur verdeckt, aber nicht überschrieben wurde. C.Z; // TMyClassB.Z wird ausgeführt C.Free; C := TMyClassA.Create; D := TMyClassB.Create; E := TMyClassB.Create; C.Y; // TMyClassA.Y C.Z; // TMyClassA.Z D.Y; // TMyClassA.Y D.Z; // TMyClassB.Z E.Y; // TMyClassB.Y E.Z; // TMyClassB.Z |
AW: Meine Probleme mit Delphi-OOP ...
Und zum published: der Abschnitt ist für die Kommunikation mit dem Objektinspektor gedacht. Also muss folglich alles, was man bereits zur Designtime zuweisen will, in diesen Abschnitt. Außerdem werden Komponenten, die hier stehen, in die *.dfm aufgenommen und aus ihr geladen. Wenn man auf diesen Komfort verzichten kann/will, dann kann man sie auch z.B. in den private-Abschnitt verschieben, muss sie dann aber auch zur Laufzeit erzeugen/freigeben und alle Werte per Code zuweisen (inkl. der Events).
|
AW: Meine Probleme mit Delphi-OOP ...
Ich geb Dir mal eine Teilantwort auf deine Fragen.
Also der Konstruktor von TComponent ist virtuell. Damit ist auch der Konstruktor aller davon abgeleiteten Klassen (insbesondere TForm) auch virtuell. Sobald ein virtueller Konstruktor existiert, ändern sich die Spielregeln für die Klassen (und alle davon abgeleiteten Klassen). Wenn man nun einen anderen Konstruktor als den vordefinierten virtuellen Konstruktor benützt bekommt man das Problem dass der selbstdefinierte Konstruktor unter Umständen nicht aufgerufen wird.
Delphi-Quellcode:
Daher gilt die Regel:
// Beispielcode
// Hier wird ein Formular erzeugt aber dabei nur der virtuelle Standardkonstruktor aufgerufen Application.CreateForm(TForm, Form1); keine neuen Konstruktoren für Klassen definieren, die von TComponent abgeleitet sind!! Nur diese Signatur ist erlaubt
Delphi-Quellcode:
public
constructor Create(AOwner: TComponent); override; destructor Destroy; override; |
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Published legt erweiterte RTTI-Informationen an. Nja, will man Designtime-Komponenten haben, muß man von TComponent erben. TComponent ist im Modus {$M+} compiliert, womit "published" als Standardabschnitt aktiv ist. (wenn vorher kein private, public und Co. angegeben wurde, so wie in meinen Beispielklassen) Ansonsten ist alles standardmäßig public. |
AW: Meine Probleme mit Delphi-OOP ...
Hallo und danke erst einmal fuer die raschen Antworten.
Also zuersteinmal zum Verdecken und Ueberladen Ich verstehe es nicht. Auch anhand des Beispiels nicht. Besser waere eine exakte Definition von Verbergen und Ueberschreiben im Zusammenhang mit Virtual (und ohne dieses). Wir haben in C++ gelernt, dass es im Zuge der Vererbung zwei Moeglichkeiten gibt, sich auf eine bereits definierte Methode zu beziehen:
Hier scheint das naders zu sein: Hier muss zum Ueberladen wohl immer die Basismethode virtual sein ... --> Dringender Erklaerungsbedarf zu dem ganzen Komplex (in Form einer sauberen, klar abgegrenzten Definition) Zum Published Ok, das mit dem Objektinspektor wusste ich. Heisst das aber, dass auch alle Methoden, die durch die Form-Komponenten benutzt werden (Ereignisbehandlungsroutinen, z. B. ButtonClick, EditKeydown ...) auch published sein muessen?? Bzw. Methoden, welche von dieses Ereignisbehandlungsroutinen benutzt werden??? Das waere ja ein Schlag in die Magengrube der Objektorientierung ... So waeren ja nur noch Felder und Methoden geschuetzt, welche nichts mit der Form zu tun haetten. Ist das richtig? (Ausgangspunkt ist natuerlich, dass ich mit dem Objektinspektor arbeiten will) Zum Virtuellen Konstruktor Also dass mein eigens entwickelter Konstruktor nicht aufgerufen wird, halte ich fuer unwahrscheinlich. Ich rufe ihn ja selbst explizit auf. In einer anderen Form. Also meine erste Form erzeugt die zweite mit dem selbstentwickelten Konstruktor. Dabei entsteht die Warnung. Auch bei overload (da ich ihn ja ueberlade). Aehnlich verfahre ich auch mit der ersten Form: Dafuer erstelle ich im Hauptprogramm eine lokale Variable, welche ich mit einem neuen Form-Objekt durch den Standardkonstruktor Create(AOwner: TComponent) initialisiere; danach kommt erst CreateForm(...). Bitte dafuer noch einmal meine Ueberarbeitung des Hauptprogramms im ersten Beitrag ansehen. Habe wie gesagt ALLE globalen Variablen (*Gaensehaut bekomm*) entfernt und eine lokale Variable fuer das Hauptprogramm angelegt. Aber zu der Aussage, die du triffst, >shmia<: Dann koennte ich ja alle Felder, die ich einer Form hinzufuege nur von aussen "nach-initialisieren", mit Set-Methoden, denn Konstruktoren darf ich laut deiner Aussage dafuer nicht schreiben. Auch das waere ein Verstoss gegen das Prinzip der OOP. Jedes Objekt soll sich selbst verwalten und die Konsistenz der eigenen Daten sicherstellen. Entschuldigt, wenn ich rechthaberisch klinge, aber genau deswegen eroeffnete ich das Thema, weil es momentan heftige Kollisionen mit meinem bisherigen Verstaendnis der OOP (aus anderen Sprachen) gibt. Bis dann, Ri |
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Dieser Weg wird in der Praxis weit häufiger benützt als das explizite Aufrufen des Konstruktors. Jedes Label, Editfeld, Menü,... wird ja normalerweise unter Zuhilfenahme der DFM-Datei dynamisch zur Laufzeit erzeugt. Genau aus diesem Grund wurde ja für Komponenten ein virtueller (Standard-)Konstruktor eingeführt. Wenn deine Komponente eine weiteren Konstruktor hat, dann wird dieser von der VCL ignoriert. Daraus kann man nur die Schlussfolgerung ziehen, dass es einfacher und sicherer ist Parameter nicht über den Konstruktor sondern über Properties dem Objekt bekannt zu machen.
Delphi-Quellcode:
Um das Objekt "b" zu initialisieren benötigt man eine Zeile mehr als für Objekt "a".
// Negativbeispiel
TDoppeltGemoppelt = class(TBasisComponent) private FAnzahl : integer; public // virtueller Standard Konstruktor constructor Create(AOwner:TComnponent); override; // nicht-standard Konstruktor constructor Create(anzahl:integer); published property Anzahl:integer read FAnzahl write FAnzahl default 100; end; constructor TDoppeltGemoppelt.Create(AOwner:TComnponent); override; begin inherited; // vererbten Konstruktor aufrufen FAnzahl := 100; // Defaultwert für property Anzahl setzen end; constructor TDoppeltGemoppelt.Create(anzahl:integer); begin // wir müssen auf jeden Fall den Standard Konstruktor aufrufen // wenn das vergessen wird, gibt es Probleme Create(nil {Owner ist hier unbekannt}); FAnzahl := anzahl; end; ..... var a, b : TDoppelGemoppelt; begin a := TDoppelGemoppelt.Create(42); b:= TDoppelGemoppelt.Create(nil); b.Anzahl := 42; Wenn aber der eigene Konstruktor so wenig Zusatznutzen bringt ist es besser ihn ganz weg zu lassen und immer nur mit dem Standard Konstruktor und Properties zu arbeiten. Komponenten in Delphi sind in der Hinsicht genau gleich wie JavaBeans. |
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Siehe auch bei ![]() Zitat:
Um sicherzustellen das dennoch Dein Objekt erzeugt wird, rufst Du die ursprüngliche Methode mit inherited auf. Also beim Konstruktor inherited aufrufen und dann Deinen eigenen Code abarbeiten lassen. Beim Destruktor zuerst Deinen eigenen Code ablaufen lassen udn inherited zum Schluss (Erklärt sich von selbst wenn man bedenkt, dass man in nicht vorhandenen Objekten keine Änderungen durchführen kann).
Delphi-Quellcode:
TBasisClass = TObject
constructor Create(AOwnder: TComponent); virtual; destructor Destroy; virtual; end; TMyClass = TBasisClass constructor Create(AOwner: TComponent); override; destructor Destroy; override; function DoSomeThing(Left,Right: Integer): Boolean; Overload; function DoSomeThing(Left,Right: String; Up,Down: Integer): Boolean; Overload; end; constructor TMyClass.Create(AOwner: TComponent); begin inherited; { Eigebner Quellcode der abgearbeitet werden soll } end; destructor TMyClass.Destroy; begin { eigener Code der abgearbeitet werden soll } inherited; end; function TMyClass.DoSomeThing(Left,Right: Integer): Boolean; begin { DoSomeThing } end; function TMyClass.DoSomeThing(Left,Right: String; Up,Down: Integer): Boolean; begin {DoSomeThingElse } end; |
AW: Meine Probleme mit Delphi-OOP ...
Warum glaubt mir denn keiner ...?? :cry:
Das, was ihr sagt, habe ich doch weiter oben alles selbst schon beschrieben ... Ich weiss, was Ueberladen und Ueberschreiben ist ... Zitat:
Zitat:
Delphi-Quellcode:
--> Das ist, was ich will. Und die Warnung bleibt bestehen, obwohl ich nix verdecken will, nur ueberladen.
constructor Create(Eigentuemer: TComponent; DieKanalnummer: Integer); overload;
constructor TGeraeteDialog.Create(Eigentuemer: TComponent; DieKanalnummer: Integer); begin inherited Create(Eigentuemer); Kanalnummer := DieKanalnummer; end; Zitat:
Delphi-Quellcode:
Vielleicht nochmal zum Verstaendnis:
var Hauptfenster: THauptfenster; //globale Variable in Unit1 geloescht!!!!! Hier LOKALE Variable angelegt
begin Hauptfenster := THauptfenster.Create(nil); //selbststaendig den Standardkonstruktor der ersten Klasse eingestellt Application.Initialize; Application.CreateForm(THauptfenster, Hauptfenster); //Hier wird die erste Form durch VCL initialiserst/virtueller Standard-Konstuktor //Hier stuende noch eine Zeile, wo die zweite Form initialisiert wuerde, aber die habe ich geloescht, denn das macht mein eigener explizit aufgerufener Konstruktor in der ersten Form; die zweite Form ist ein Attribut der ersten Form Application.Run; end. Mein Programm besteht aus zwei Forms, eine wird im Hauptprogramm durch die VCL initialisert (wie oben zu sehen), die andere wird als Attribut der ersten mit einem expliziten Konstruktor UND NICHT DURCH DIE VCL erstellt. Dabei entsteht die Warnung. |
AW: Meine Probleme mit Delphi-OOP ...
Delphi-Quellcode:
PS: Jetzt steht in Hauptfenster der Instanzzeiger der automatisch erstellten Instanz, aber (da Visible der Formulare standardmäßig) False ist, wird hier eventuell nur das zuerst erstellte Fenster angezeigt und nicht das jenes, welches in der Variable gespeichert ist.
Hauptfenster := THauptfenster.Create(nil); // hier erstellst du manuell eine Instanz von TForm.
Application.Initialize; // und daß Erstellen auch noch vor dem Initialisieren der VCL Application.CreateForm(THauptfenster, Hauptfenster); // und hier wird nochmal ein eine Instanz erstellt. |
AW: Meine Probleme mit Delphi-OOP ...
Ja, ok das stimmt.
Diesen ersten Konstruktor kann ich entfernen. Da ist richtig. Aber es geht ja auch darum, dass ich hier eine lokale Variable erstellt habe. Dazu sagt z. B. niemand was. Und es ging ja auch mehr um den ueberladenen Konstruktor der anderen Form, denn der verursacht die Warnung. Und naja, um die ganzen anderen Probleme die bisher ungenuegende Beruecksichtigung fanden. :-) |
AW: Meine Probleme mit Delphi-OOP ...
Hat was damit zu tun das Create urspünglich virtual deklariert wurde
Delphi-Quellcode:
führt auch zu
TMyClass=Class
Procedure TuWas(a:Integer); Procedure TuWasvirtual(a:Integer);virtual; End; TMyClass1=Class(TMyClass) Procedure TuWas(a:Integer;b:Integer);overload; Procedure TuWasvirtual(a:Integer;B:Integer);overload; End; [DCC Warnung] Unit2.pas(16): W1010 Methode 'TuWasvirtual' verbirgt virtuelle Methode vom Basistyp 'TMyClass' |
AW: Meine Probleme mit Delphi-OOP ...
Obwohl ich sie eigentlich gar nicht verbergen moechte, sondern nur ueberladen?
|
AW: Meine Probleme mit Delphi-OOP ...
Mal als Kommentar von der Seite, von jemandem, der OOP-mäßig ja noch in die Schule geht und da mit C# rumhampeln muss. Wenn du in einer abgeleiteten Klasse den Konstruktor überlädtst, dann überschreibst du ihn auch, mein ich. Sprich also du musst (zumindes in C# machen wir das so) in der abgeleiteten Klasse erst den Standardkontruktor überschreiben mit einem Standardkonstruktor und dann diesen überladen. Vllt. verwechsle ich das aber auch.
|
AW: Meine Probleme mit Delphi-OOP ...
@Jumpy: Genauso ist es meines Erachtens auch korrekt. Da der Konstruktor in der Basislkase als virtuell deklariert wurde muss er in der abgeleiteten Klasse zuerst überschrieben werden (mit override) und wenn man dann weitere Konstruktoren mit variieender Parameterliste hinzufügen will, werden diese überladen (overload), so habe ich es auch gelernt. In wie weit man hinter den ersten Konstruktor allerding override und overload zugleich setzen kann, weiß ich jetzt aus dem Stehgreif auch nicht, werde ich aber mal testen.
@trebor90: In welche Variable (lokal oder global) Du ein Objekt erzeugst interresiert doch den Konstruktor nicht. Aber ich verstehe jetzt auch nicht ganz, wieso Du solche Probleme mit Polymorphie hast, gehört doch auch zur ![]() Kann man z.B. auch auf Wikipedia nachlesen... Zitat:
|
AW: Meine Probleme mit Delphi-OOP ...
Also dass ich eine virtuelle Methode bzw. einen Konstruktor erst in einer abgeleiteten Klasse ueberschreiben muss, dass ich sie/ihn dann ueberladen kann - davon habe ich noch nie etwas gehoert.
Ich kann doch Sachen ueberladen, ohne sie vorher ueberschreiben zu muessen (egal ob virtuell oder nicht). Und dass ich das mit den globalen und lokalen Variablen anspreche: Hat nix mit dem Konstruktor zu tun. Nur, dass ich nicht mit grossen Fensterobjekten ueber globale Variablen agieren moechte, sondern, wie es sich gehoert, sie in lokale packe. Und darauf hinwies. Die Frage waere naemlich, wie "ihr" das so macht? Lasst ihr den vorgefertigten Delphi-Quelltext so
Delphi-Quellcode:
oder aendert ihr das im Hauptprogramm in eine lokale Variable?
var
Form1: TForm1 implementation ... -- |
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Zitat:
Zitat:
|
AW: Meine Probleme mit Delphi-OOP ...
Hallo,
ich gebe auch mal meinen Senf zum Thema überladen von virtuellen Methoden dazu. Entweder so:
Delphi-Quellcode:
Oder so:
TTest1= class
procedure Test; virtual; end; TTest2= class(TTest1) procedure Test(const A: Integer); reintroduce; overload; end;
Delphi-Quellcode:
einbeliebigername.
TTest3= class
procedure Test; overload; virtual; end; TTest4= class(TTest3) procedure Test(const A: Integer); overload; end; |
AW: Meine Probleme mit Delphi-OOP ...
@einbeliebigername:
Ok, dann ist das anders als zum Beispiel in C++. Da muss man Methoden nicht erst reintroducen, um eine Methode einer Basisklasse ueberladen zu koennen. Man ueberlaedt sie und - fertig. Das heisst also, der Designer der Basisklasse legt in Delphi fest, ob man seine Methoden "einfach so" (ohne reintroduce) ueberladen kann?? Indem er hinter die betreffenden ein overload setzt????! @mkinzler: Nein. Warum denn eine Klasseneigenschaft?! Du hast mich wohl falsch verstanden. Ich rede nicht von pubnormalen Eigenschaften die faelschlicherweise oft als globale Variablen deklariert werden. Ich rede von einer Form, einem Fenster. Das Hauptfenster zum Beispiel, dass als erstes in der Applikation gestartet wird, muesste also eine lokale Variable der Hauptfunktion sein. (Bitte schaut euch dazu meine Quelltexte an ...) Sicherlich kann das jedes weitere Fenster eine Eigenschaft des Hauptfensters (und keine globale Variable) sein. Zitat:
Zitat:
Zitat:
Ich will doch gar nix ueberSCHREIBEN. Ich wollte damit sagen, dass ich Methoden ueberladen kann. Dazu muss ich sie nicht ueberschreiben. Egal ob sie virtuell sind oder nicht. Ueberladen ohne sie vorher zu ueberschreiben, wie es einige sagen. Ich muss Methoden nicht erst ueberschreiben, dass ich sie hinterher ueberladen kann. --> Und in C++ muss eine Methode auch nicht virtuell sein, dass ich sie ueberschreiben kann. Das geht auch ohne. Problem aber immer noch: Was ist der Unterschied zwischen Verdecken und Ueberschreiben?? Ich kenne sowas wie "Verdecken" gar nicht. |
AW: Meine Probleme mit Delphi-OOP ...
So, jetzt wirds richtig konfus.
Also erstmal zum Thema lokale Variable: Wie muss ich das verstehen? Du ordnest Deinem MainForm eine lokale Variable unter, in die Du dan diese MainForm erzeugen willst? Meinst DU damit, dass Du diese Variable als Feld in Deinem MainForm hinterlegst? Dann würdest Du ja Dein Form in sich selbst erzeugen und kannst eigentlich erst richtig auf Dein Feld zugreifen, wenn Du Dein Form (das Objekt) erzeugt hast... Das klingt seltsam. Meine MainForm ist grundsätzlich in einer globalen Variable hinterlegt, es sei denn ich würde das MainForm aus einem eigenen Objekt heraus erzeugen und mich selbst um die Verwaltung (erzuegen, verwalten, freigeben) kümmern, bzw. einen Parent angeben, ansonsten überlasse ich das vollkommen Delphi. Dann zum Thema überladen und reintroduce. Auf reintroduce bin ich nicht eingegangen, da Du es unbedingt vermeiden wolltest, weshalb auch immer. Sieh das ganze mal aus der Sicht des Compilers. Du hast ein, von einer Basisklasse abgeleitetes, Objekt (in OOP ganz normal) dessen Konstruktor in der Basisklasse als virtuell deklariert ist (nicht abstrakt, sonst müsstest Du es auf jedenfall selbst beleben, siehe z.B. TStrings). Wenn Du nun einen eigenen Konstruktor Create erzeugst und nicht angibst, dass es sich um eine mögliche Erweiterung Deinerseits zum ursprünglichen Create, dann gibt es jetzt zweimal Create und der Compiler warnt Dich, dass Dein, in Deiner abgeleiteten Klasse angegebenes Create, den originalen virtuell deklarierten Konstruktor Create verdeckt. Da Du aber beim überladen den Befehl overload hinter beide Konstruktoren setzen musst, ist es zwingend erforderlich dieses dem Compiler mitzuteilen. Daher kannst Du entweder bereits den originalen Konstruktor überschreiben und erweitern, ein override udn ein overload hinzufügen und dann einen weiteren Konstruktor mit overload hinzusetzen, wenn seine Parameterliste sich vom ersten unterscheidet. Willst Du den originalen Konstruktor nicht erweitern, dann teilst Du dem Comiler mit reintroduce udn overload mit, dass es sich bei dem ersten Konstruktor um den originalen aus der Basisklasse handelt und das Du weitere überladene Konstruktoren für andere Zwecke hinzufügst. In wie weit es in einer anderen Sprache anders gehandhabt wird ist uninteressant, ich kann auch nicht diskutieren, dass ich es in C++ blöd finde immer diese {} zu setzen, wo das doch in Delphi auch nicht so ist und aus meiner Sicht nur die Lesbarkeit des Quellcodes verschlechtert. Innerhalb der Zeit, in der wir hier unzählige Symbole aneinandergefügt haben um miteinander zu diskutieren, hätte man zigmal reintroduce schreiben können, egal ob man es blöd findet oder nicht. Aber bitte diese Antwort nicht bös auffassen, sie ist nicht so polemisch gemeint, wie sie vielleicht klingen mag, wenn man sich in ein Thema hineingesteigert hat. Viele Grüße. |
AW: Meine Probleme mit Delphi-OOP ...
Liste der Anhänge anzeigen (Anzahl: 1)
Praktisch gesehn, werden diese globalen Form-Variablen garnicht benötigt, welche Delphi standardmäßig anlegt.
Ich würde mir wünschen, wenn es dieses CreateForm auch ohne diesen Var-Parameter geben würde. Aber mit einer temporären Variable in der DPR, könnte man dieses auch lösen. Vorallem in einem Programm mit nur einem automatisch erstellten Form, gibt es IMHO absolut keine Daseinsberechtigung für diese Variable und ohne diese gäbe es viele (Anfänger)Probleme ganicht erst. PS: Das angehängteDemoprojekt enthält einen Fehler, wie es genau so schon vorgekommen ist, bei einem Anfänger/Schüler, letztes Jahr hier in der DP. Und er zeigt ein großes Problem, welches mit globalen Variablen auftreten kann, wenn sie nicht eindeutig verwendet, sondern mehrmals und/oder an mehreren Stellen genutzt werden. Ach ja, ich suche noch eine einfache Demo, für eine globale Zählvariable, welche in mehreren Funktionen genutzt werden und sich so gegenseitig beeinflussen, was mit lokalen Schleifenvariablen nicht passieren würde. Falls jemand was kennt.... (mit ist der Thread aus'm letzten/vorletzen Jahr nicht wieder eingefallen, wo sowas mal aufgetreten ist) |
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
![]() :wink: |
AW: Meine Probleme mit Delphi-OOP ...
Vielleicht hilft das hier weiter:
![]() |
AW: Meine Probleme mit Delphi-OOP ...
Hallo,
Zitat:
Delphi-Quellcode:
Kommt aber trotzdem die Warnung:
TTest5= class(TTest1)
procedure Test; overload; override; procedure Test(const A: Integer); overload; end;
Code:
[DCC Warnung] MethodTracerUnit1.pas(32): W1010 Methode 'Test' verbirgt virtuelle Methode vom Basistyp 'TTest1'
Zitat:
Code:
In Delphi ist jede normale Funktion, Prozedur, Methode nicht überladbar. Will man überladen muss man bei jeder Version ein overload mit angeben. Folgendes geht deswegen so nicht:
void Test;
void Test(int A);
Delphi-Quellcode:
Der Kompilier wirft da einen Fehler. Auch so:
procedure Test;
procedure Test(const A: Integer);
Delphi-Quellcode:
und so gibt es einen Fehler:
procedure Test;
procedure Test(const A: Integer); overload;
Delphi-Quellcode:
Erst so funktioniert das Überladen:
procedure Test; overload;
procedure Test(const A: Integer);
Delphi-Quellcode:
Und weil das auch bei Vererbung so ist, braucht man für jede andere Version des Create-Constructors bei Komponenten ein
procedure Test; overload;
procedure Test(const A: Integer); overload;
Delphi-Quellcode:
.
reintroduce;
Zitat:
Zitat:
Delphi-Quellcode:
Das Test von TTestB verdeckt das Test von TTestA. Sprich folgendes lässt sich nicht kompilieren:
TTestA= class
procedure Test; end; TTestB= class(TTestA) procedure Test(A:Integer); end;
Delphi-Quellcode:
Die Fehlermeldung wäre:
...
B: TTestB ... B.Test; ...
Code:
einbeliebigername.
[DCC Fehler] MethodTracerUnit1.pas(119): E2035 Nicht genügend wirkliche Parameter
|
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Was ich aber dennoch nicht ganz verstehe, weshalb eine feste Definition innerhalb einer Sprache nicht einfach hingenommen werden kann. Es gibt ja auch noch die Unterschiede bei div zwischen C++ und Delphi, wobei ich in dieser Hinsicht sagen muss, dass mir Delphi da besser gefällt. Auch dass es bei Delphi in der Grundeinstellung keine Header-Dateien gibt wie in C++ wurd enicht bemängelt, obwohl man dann von ganz unten bis nach ganz oben scrollen mus, anstatt einfach in den Header zu sehen wenn man Variablen oder Deklarartionen nochmals bearbeiten möchte. |
AW: Meine Probleme mit Delphi-OOP ...
overload = überladen
override = überschreiben überladen = gleichzeitig mehrere Methoden mit gleichem Namen, bzw. alte aus Vorfahren Methode nicht verdecken. (absichtliches Verdecken wird mit reintroduce angeben) überschreiben = virtuelle (virtual) oder dynamische (dynamic) Methode des Vorfahren überschreiben, also den Eintrag in der VMT ändern. scrollen ... dafür ibt es Listen und Tastenkürzel, im schnell hin- und herzuspringen, wie z.B. Strg+Shift+Hoch oder +Runter |
AW: Meine Probleme mit Delphi-OOP ...
@himitsu: Ist dies eine allgemeingültige Definition und somit auch auf Delphi übertragbar? Ich wäre jetzt sehr dankbar, wenn Du mir erklären würdest, warum der Delphi-Compiler dies anscheinend anders sieht.
Abgesehen davon, wenn man mit reintroduce absichtlich eine Methode überdeckt, aber mit inherited dafür sorgt, dass die originale Methode dennoch ausgeführt wird, was soll daran so schlimm sein? Ich verstehe eben nach wie vor nicht, wen es gewisse Regeln zu geben scheint, an die man sich einfach zu halten hat, warum es so schwer ist genau dies zu tun? Ich möchte hier niemandem mit dem Finger drohen, wie es anscheinend rüberkommt. Denke aber immer daran, dass die ursprüngliche Definition der OOP zugunsten der Praktikabilität nicht vollkommen umgesetzt wurde (z.B. bei der Polymorphie). Zitat:
|
AW: Meine Probleme mit Delphi-OOP ...
Wegen den anderen fragen schau ich dann nochmal nach.
Aber reintroduce schaltet eigentlich nur die Compilerwarnung ab. Wenn man eine Methode verdeckt, dann wird man vom Compiler gewarnt. Und hat man absichtlich verdeckt, dann kann man das über reintroduce auch so markieren und weg ist die Warnung. Ob die verdeckte Methode intern dann via inherited oder sonstwie aufberufen wird, ist dabei unwichtig. Das Verdecken bezieht sich nur auf die äußere Schnittstelle, also das Interface (Deklaration) und nicht die Implementation (Code). |
AW: Meine Probleme mit Delphi-OOP ...
@himitsu: Besten Dank, wird notiert. Vielleicht nehme ich auch viel zu viel einfach als gegeben hin.
|
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Overload, Override ist zumindestens in Delphi/Pascal so umgesetzt. Virtual und Override ist so: Gibt man in einer Klasse virtual für eine Methode an, dann wird dafür, in/ab diesem Typ ein Eintrag in der VMT angelegt. Wenn man jetzt eine Variable mit diesem Typ anlegt, dann kennt dieser Typ seine VMT. Nun könnte man in einer Variable auch einen Nachfahren dieses Typen abspeichern. Ist in dem abgeleiteten Typen diese Methode überschrieben, dann wird das in der VMT vermerkt. Damit kennt dann auch der Vorfahr den Zeiger zur neuen Methode, da es ja in der VMT drinsteht. Wurde diese Methode aber nur verdeckt (egal ob sie Virtual ist oder nicht), dann kennt der Vorfahr diesen Zeiger nicht und kann damit immer nur seine Methode aufrufen. Aus diesem Grund sind alle VCL-Komponenten von TComponent abgeleitet, wo vorallem der Constructor virtuell ist, sowie bei allen Klassen der Destructor virtual ist. Auf diese Weise kann die VCL den richtigen Constructor laden und auch immer der richtige Destructor ausgeführt werden, da der Vorfahre diese kennt. |
AW: Meine Probleme mit Delphi-OOP ...
Zitat:
Verdecke ich ich einfach die original Methode, dann wird hier kein Eintrag in der VMT hinterlegt und ich muss sehr genau wissen ab welcher Stufe der Vererbung die entsprechende Methode mit reintroduce verdeckt wurde. Wie verhält es sich nun bei Weiterveerbung? Sidn die Subklassen dann in der VMT ab hier auf die verdeckte Methode des Vorfahr bezogen oder wieder aufgrund der VMT auf die Superklasse? Ich habe tatsächlich sehr viel dazugelernt. Bisher hätte ich nicht erwartet, dass Delphi sich im Gegensatz zu anderen OOP-Sprachen doch so entscheidende Abweichungen erlaubt. Darüber hinaus habe ich auch mal Experimente mit der Mainform angestellt, weil ich jetzt wirklich wissen wollte, ob die globale Variable für das MainForm überflüssig ist. Und uups, in der Tat es ist so, wenn ich das Fensterhandle des MainForms auslesen will, muss ich Application.Handle verwenden und tatsächlich nicht MainForm.Handle. Vielen Dank für diesen Augenöffner. |
AW: Meine Probleme mit Delphi-OOP ...
Mit der neuen RTTI könnte man zwar theoretisch auf auf die VMT und virtuelle Methoden verzichten, da man ja die Funktionen alle suchen und über die RTTI aufrufen könnte. (solange man die neue RTTI nicht einschränkt/abschaltet)
Bzw. über Published-Methoden der alten RTTI ginge es auch schon. Aber schneller ist es über die VMT, denn ein indizierter Zugriff und man hat die aktuelle Methode. Nachteil über die RTTI: Man könnte Methoden nicht verdecken, da sie dann ja automatisch überschrieben würden, wenn man ausnahmslos das Aktuellste sucht. Und Klassenmethoden über eine Objektinstanz aufgerufen wären vermutlich nicht überschreibbar. |
AW: Meine Probleme mit Delphi-OOP ...
Übrigens hat man im neuen C++ Standard ein (optionales) override eingeführt, weil die bisherige Methode zu sehr subtilen Fehlern führen kann.
![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:09 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz