![]() |
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. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:31 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