![]() |
Fragen zu OOP und Klassen
Ich will Klassen schreiben, nur verstehe ich kaum was davon. Ich hab mir einige Bücher, Tutorials und sonstige Onlinekurse durchgelesen, aber sie beantworten nicht alle meine Fragen. Es wird mir zu viel als gegeben angenommen oder wichtige Punkte mit einem Satz abgehandelt. Bei vielen Sachen will ich aber genauer wissen wieso es so funktioniert oder noch wichtiger, wieso es trotzdem funktioniert, obwohl ich es eigentlich anders machen sollte. Die ganzen Tutorials und Kurse werden anscheinend von Leuten geschrieben die die Materie beherrschen und sich deshalb bestimmte Fragen nicht mehr stellen. Deshalb behandeln sie sie auch nicht oder nicht so genau. So muß man bei Kursen einiges einfach akzeptieren ohne zumindest am Anfang zu verstehen wieso es so ist. Deshalb würde ich gerne hier einen Ergänzungskurs anfangen bei dem ich die Fragen stelle die für einen Anfänger interessant sind. Mir ist klar, daß es die Regel gibt pro Thread nur eine Frage zu stellen, aber ich hoffe man macht hier eine Ausnahme, so daß das ganze Thema zusammen bleiben kann. Also verschiedene Fragen aber zum Thema Klasse.
Ich hab viele Fragen, also fange ich mal an und nicht alles auf einmal. Eine Klasse muß nicht kompliziert sein. Sie kann auch so aussehen:
Delphi-Quellcode:
Das ist eine einfache Klasse und ich kann sie sofort nutzen ohne sie weiter auszubauen.
TYPE
TTest1 = class a: Byte; end;
Delphi-Quellcode:
Da bei Klassen geerbt wird, hat meine Klasse auch einiges von seinem Vorfahr geerbt, darunter auch die Methoden Create und Free. Da ich keine Klasse als Vorfahr genannt habe, wird TObject automatisch angenommen. Somit erbt meine Klasse alle Methoden von TObject. Baue ich auf einem anderen Vorfahrtyp, muß ich den angeben.
procedure TForm1.Button1Click(Sender: TObject);
var Test1: TTest1; begin Test1 := TTest1.Create; Test1.a := 200; ShowMessage(IntToStr(Test1.a)); Test1.Free; end; Machen wir es jetzt komplizierter und geben a einen Anfangswert.
Delphi-Quellcode:
Immer noch eine einfache Klasse, aber hier bekommt a in Create einen Anfangswert. Das Ganze funktioniert übrigens ohne inherited. Liest man sich aber Bücher und Tutorials durch, so steht an erster Stelle, egal wie einfach eine Klasse ist und ob die sich von TObject ableitet, in Create immer inherited. Bei mir nicht. Hab ich jetzt ein Sakrileg begangen? Ich stelle an dieser Stelle mal die Prinzipfrage ob inherited immer nötig ist. Laut etlicher Bücher und Kurse ja, denn das macht man nun mal so. Genauer wird an dieser Stelle selten eingegangen. Create, wie übrigens auch Destroy, von TObject ist nebenbei gesagt leer und ohne Inhalt. Wozu also hier Create mit inherited aufrufen? Wie man merkt möchte ich hier etwas über inherited diskutieren, da überall nur steht, daß es genommen werden soll, ohne genauer drauf einzugehen.
TYPE
TTest1 = class(TObject) a: Byte; constructor Create; end; constructor TTest1.Create; begin a := 1; end; inherited ist dazu da um den Vorfahr aufzurufen. Baue ich auf einer Klasse auf und will nicht nur den Namen erben, sondern auch die Eigenschaften die sich im Vorfahr hinter der Funktion verbergen, dann muß ich den Vorfahr mit inherited aufrufen. Wie ich allerdings in vielen Beispielen sehen konnte wird inherited genommen ohne groß zu überlegen. Einen Fehler macht man anscheinend nicht wenn man inherited in eine Klasse einbaut die direkt von TObject abstammt, aber wozu nehmen wenn nicht nötig. Das gleiche gilt wohl auch für Destroy. Eine andere Frage ist wozu inherited bei Create immer vorne und bei Destroy immer hinten genommen wird? Rufe ich bei Create mit inherited den Vorfahr der Klasse auf, ist die Sache klar. Zumindest wenn ich etwas nutze was mir der Vorfahr liefert. Nutze ich nichts vom Vorfahr am Anfang, sondern ergänze den nur, ist es doch egal wo inherited steht. Beispiele wie es nicht üblich ist:
Delphi-Quellcode:
Das gleiche gilt wohl auch beim Destroy.
constructor TTest2.Create;
begin StringList := TStringList.Create; inherited; end;
Delphi-Quellcode:
Ich zähle das Ganze nur wegen dem Verständnis auf. Man kann inherited in jedes Create schreiben, muß es aber nicht wenn es nicht notwendig ist. Wenn man es aber nicht weiß ob es notwendig ist, dann ist es nicht falsch es einzusetzen. Schaden tut es nicht. Bei der Reihenfolge muß inherited bei Create auch nicht immer an erster Stelle stehen, wenn es nicht notwendig ist. Wenn man es aber nicht weiß ob es notwendig ist, dann ist es nicht falsch Create an erster Stelle zu setzen. Schaden tut auch das in der Regel nicht. Hab ich das richtig verstanden?
constructor TTest2.Destroy;
begin inherited; StringList.Free; end; Ansonsten ist mir nicht ganz klar wieso Delphi das nicht automatisch erledigt. Warum ruft der constructor nicht automatisch den Vorfahr auf? Oder gibt es Situationen wo man den Vorfahr mit Absicht nicht aufruft? Oder ist das mit der Reihenfolge so wichtig, daß man es den Programmierer überläßt. Oder hat das mit constructor und destructor selbst nichts zu tun, denn bei jeder Methode und Eigenschaft muß aufs Neue überlegt werden ob und wo man inherited einsetzt? Dann folgt bei mir die Frage woher ich wissen soll wo ich inherited wann einsetzten soll? Bei Create und Destroy ist das mehr oder weniger bekannt und deshalb geregelt. Wie aber ist das bei anderen Eigenschaften und Methoden? Wie sieht es bei Assign aus? Woher soll ich wissen wo es hingehört? Bei TStrings.Assign ist inherited hinten, bei TStringList.Destroy ist es sogar mitten drin. Bedeutet das letztendlich, daß wenn ich eine Klasse schreibe ich mich erst mit dem Vorfahr auseinendersetzen muß? Wie gesagt, destroy von TStringList ist mitten drin. Das waren meine ersten Verständnisfragen. Kann man also abschließend sagen, daß wenn man Klassen schreibt man sich immer erst mit der Vorgängerklasse auseinandersetzen muß? Und lediglich bei einfachen Klassen die auf TObject aufbauen man sich die Arbeit sparen kann, da in der Regel dann nur Create und Destroy angepaßt werden und bei denen macht es keinen Unterschied ob man inherited nimmt oder nicht oder vorne oder hinten? |
Re: Fragen zu OOP und Klassen
Du hast recht bei deinenen Beipsilen wäre es überflüssig. Man macht es aber meist trotzdem nur der Vollständigkeit halber. Bei CReate als erstes, weil man gegebenefalls Sachen im Konstruktor hat, die der Konstruktor der Vorfahrenklasse nicht wieder kaputt machen soll. Deswegen ruft Delphi den Konstruktor der Vorfahrenkalsse auch nicht automatisch auf, weil das nicht immer unbedingt gewünscht ist. Und bei Destroy als letztes, weil man erstmal seine abgeleitete Klasse aufräumt.
Guck dir mal mein Tutorial an: http//delphitutorials.michael-puff.de |
Re: Fragen zu OOP und Klassen
Hi, erstmal vorweg finde ich Deine Idee gut, da ich damals (als ich die OOP verstehen wollte) auch kein wirklich gutes Tut. gefunden hatte. Ist allerdings auch ein paar Jahre her, vielleicht sind die ja schon besser geworden. Trotzdem ist es immer gut zu fragen und natürlich werden die Fragen auch gerne beantwortet.
Zitat:
Hierin liegt auch díe eigentliche Begründung für die Position von inherited. Das Create eines TObject hat die grundlegenden Dinge zu erledigen, es muss Speicher alloziert, diese Tabelle erzeugt und ein Verweis zurückgegeben werden. Vieles von dem Code bleibt aber verborgen, da es sehr spezifisch ist. Deshalb wirkt das Create nur leer, tatsächlich wird auch dort (natürlich) Code ausgeführt. Es macht natürlich für jede Klasse Sinn, dass man erst den nötigen Speicher alloziert usw. und erst dann weitermacht. Dass es auch ohne klappt hat nicht viel zu sagen, das kann auch von Fall zu Fall mal ordentlich nach hinten losgehen. Da gibt es dann eine Access Violation. Ebenso ist das Destroy von TObject für das Aufräumen des Speichers zuständig. Dabei wird eben das eigentliche Objekt (also z.B. auch die genannte Tabelle) aus dem Speicher entfernt. Entsprechend kannst Du nicht mehr auf das Objekt zugreifen, wenn das inherited Destroy aufgerufen wurde. Die anderen Arbeiten die im (eigenen) Destruktor stattfinden sind i.d.R. das Aufräumen von Datentypen, auf die nur ein Verweis gespeichert wurde (dyn. Arrays, Klassen, Zeiger). Dort würde das inherited sonst nur den Speicher für den Verweis löschen, die eigentlichen "Objekte" (hier nicht im OO-Sinne gemeint) würden einfach unerreichbar im Speicher verbleiben. So, muss leider gerade los, werde aber bei Gelegenheit mit noch den Rest anschauen und antworten. Gruß Der Unwissende |
Re: Fragen zu OOP und Klassen
Zitat:
Zitat:
Du hast mit den virtuellen und statischen Methoden etwas dem Thema vorweg gegriffen. Auf das Thema wollte ich etwas später eingehen. Auch da habe ich Fragen, aber alles der Reihe nach, sonst kommen wir durcheinander. Allerdings kann man insoweit schon darüber diskutieren ob Create ohne overload das Create des Vorgängers ersetzt oder überschreibt. Ich bin zwar noch nicht soweit, aber dann würde sich wirklich die Frage stellen ob man immer inherited aufrufen sollte. |
Re: Fragen zu OOP und Klassen
Zitat:
|
Re: Fragen zu OOP und Klassen
Hallo,
Zitat:
Der eigentliche Grund für das inherited auch bei TObject ist, dass Codegear ja jederzeit auf die Idee kommen können, Konstruktor oder Destruktor von TObject zu verwenden. Wenn dann in den Ableitungen das inherited fehlt... Gruß xaromz |
Re: Fragen zu OOP und Klassen
Schau dir mal
![]() Der "erste Aufruf" des Constructors führt also die Funktion ClassCreate aus, welche dem Objekt Leben einhaucht/ Speicher reserviert. Mit inherited rufst du nur den Vorfahr als ganz normale Methode auf. Wann und ob du das machst ist vollkommen dir überlassen. Und dabei ist es egal ob es sich um einen Constrcutor, einen Destructor oder eine Methode handelt. Warum wird es nun standardmäßig so gehandhabt, wie du beschreibst? a) Bei einer direkten Ableitung von TObject ist es derzeit nicht notwendig, da der Constrcutor von TObject ja leer ist. Aber wer sagt, dass es in Zukunft so bleiben wird. Falls es CG mal einfällt und in der Version 2008 etwas wichtiges im Constructor von TObject steht, dann brauchst du nicht deine ganzen constructors ändern. Ausserdem gibts seit :gruebel: 2006 Class Helper. Damit kannst du den Constructor in einem Projekt im Nachinein ändern. Aber es bleibt trotzdem deine Entscheidung ob du Änderungen in TObject mitmachen willst. b) Die Position des inherited erwies sich bisher so als am günstigsten. Wenn es irgendwie wichtig wist, die Reihenfolge zu beachten, dann meist in der beschriebenen Reihenfolge. Wenn es nicht wichtig war, dann kann man es auch so machen. Stört ja niemanden. |
Re: Fragen zu OOP und Klassen
Zitat:
|
Re: Fragen zu OOP und Klassen
Letzendlich, wie schon von dir erkannt und mehrfach gesagt, ist das inherited im Moment bei einer Klasse, die von TObject abgeleitet ist nicht notwendig.
Du sagst jetzt du kannst dir nicht vorstellen, daß man das Create von TObject verändern wird. Völlig ok deine Meinung. Es bleibt aber immer noch eine Meinung bzw. ein Glaube, es gibt keine Garantie dafür. Man sollte stets defensiv programmieren und genau in diesem Moment ist das einfügen des inherited ein defensives Verhalten, was dich auf die sichere Seite bringt. Mal ein kurzes anderes Beispiel zum defensiven Programmieren:
Delphi-Quellcode:
procedure TuWas();
var LMyObj: TEinTyp: begin LMyObj := TEinTyp.Create; LMyObj.MachMalDeineAufgabe(); LMyObj.Free; end; das ganze nochmal defensiv:
Delphi-Quellcode:
Im ersten Fall könnte dein Objekt freigegeben werden, im zweiten Fall wird es garantiert freigegeben.
procedure TuWas();
var LMyObj: TEinTyp: begin LMyObj := TEinTyp.Create; try LMyObj.MachMalDeineAufgabe(); finally LMyObj.Free; end; end; |
Re: Fragen zu OOP und Klassen
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:19 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-2025 by Thomas Breitkreuz