Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Constructor in der Vererbung (https://www.delphipraxis.net/181182-constructor-der-vererbung.html)

Captnemo 22. Jul 2014 17:00

Constructor in der Vererbung
 
Hi,

ich habe mir eine Basisklasse erstellt:
Delphi-Quellcode:
type
  TBefehl = class(TObject)
  private
    fGUID: Ansistring;
    constructor Create;
  published
    ...
  public
    ...
  end;
Published und Public hab ich hier mal weggelassen.

Im Constructor Create wird eine GUID erzeugt, die in fGUID gespeichert wird.

Jetzt erstelle ich mir noch eine Klasse aus meiner Basisklasse:

Delphi-Quellcode:
type
  TECHO = class(TBefehl)
  private
    ...
  published
    ...
  public
    ...
  end;
Auch hier hab ich mal die Variablen, Properties, Proceduren und co. weggelassen.

Grundsätzlich hätt ich jetzt gedacht, meine Klasse TECHO (hat keinen eigenen Constructor) erbt von der TBefehl den Constructor Create, so wie die Klasse TBefehl eben diesen von TObject erbt.
Aber, mein Constructor wird nie ausgeführt.

Hab ich das falsch verstanden und mache einen Denkfehler?

FaTaLGuiLLoTiNe 22. Jul 2014 17:08

AW: Constructor in der Vererbung
 
Wenn du von ausserhalb deiner Klassenhierarchie TBefehl.Create bzw. TECHO.Create verwendest, dann wird nicht der private Konstruktor der Klasse TBefehl aufgerufen (der ist ja nach aussen auch gar nicht sichtbar), sondern der öffentliche Konstruktor von TObject. Und der macht so ziemlich gar nichts.

Du kannst ja mal probieren, TBefehl.Create zu verwenden (von ausserhalb der Klasse TBefehl). Du müsstest eine Fehlermeldung bekommen.


// Edit: Private Konstruktoren machen eigentlich nur Sinn, um von innerhalb derselben Klasse (z.B. in einer Factorymethode) verwendet zu werden. An deiner Stelle würde ich den Konstruktor von TBefehl einfach als public deklarieren und je nachdem auch noch virtuell machen.

DeddyH 22. Jul 2014 17:11

AW: Constructor in der Vererbung
 
Ist ein privater Konstruktor nicht an sich schon ziemlich sinnentleert?

Der schöne Günther 22. Jul 2014 17:12

AW: Constructor in der Vererbung
 
Leider wäre er nur vollkommen sinnentleert wenn Delphi nicht so eine komische Definition von Sichtbarkeiten hätte. "Private" heißt ja nur "Diese Klasse und gleich noch alles in dieser Unit auch". "Strict private" wäre vollkommen sinnentleert. :stupid:

Noch am Rande: Warum ist fGuid ein String und keine TGUID?

Captnemo 22. Jul 2014 17:20

AW: Constructor in der Vererbung
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1266357)
Leider wäre er nur vollkommen sinnentleert wenn Delphi nicht so eine komische Definition von Sichtbarkeiten hätte. "Private" heißt ja nur "Diese Klasse und gleich noch alles in dieser Unit auch". "Strict private" wäre vollkommen sinnentleert. :stupid:

So hatte ich das auch gelesen. Also innerhalb der gleichen Unit sollte das ja gehen.
Da TEcho sich in der gleichen Unit befindet, dachte ich, da ich diese aus TBefehl ableite, dass sie auch deren Create verwendet. Die Klasse TBefehl wird in meinem Prog nie direkt erzeugt, sondern dient nur als Basis für anderen Objecte.

Aber: Constructor in Public....schon geht's. Gut, dann hab ich das vielleicht nicht ganz richtig verstanden.
Wenn ich mein Object TECHO in der gleichen Unit erzeugt hätte, hätte es vielleicht auch so funktioniert.

Zitat:

Zitat von Der schöne Günther (Beitrag 1266357)
Noch am Rande: Warum ist fGuid ein String und keine TGUID?

Weil ich im Grunde innerhalb meiner Objecte irgendeinen eindeutigen Namen brauchte, den aber als Ansistring. Hätt natürlich auch als TGUID machen können. Aber dann hätt ich sooft umwandeln müssen, also gleich als String;

Der schöne Günther 22. Jul 2014 17:23

AW: Constructor in der Vererbung
 
Wenn du beide in die gleiche Unit gepackt hast (Igitt!) dann sollte es eigentlich gehen! Kannst du ein reproduzierbares Minimalbeispiel einstellen?


Delphi-Quellcode:
program Project2;

{$APPTYPE CONSOLE}

{$R *.res}

type

   TBaseClass = class
      (*strict*)private constructor Create();
   end;

   TSubClass = class(TBaseClass)
      // Nichts
    end;

{ TBaseClass }

constructor TBaseClass.Create();
begin
   WriteLn('Hallo');
end;

begin
   TSubClass.Create();
   ReadLn;
end.

himitsu 22. Jul 2014 17:39

AW: Constructor in der Vererbung
 
Dein Constructor wird ausgeführt, wenn er an der Stelle des Aufrufs sichtbar ist. (also nur innerhalb der Unit)

PS: in der RTTI gibt es eine Klasse, da wollte Emba unbedingt allen "fremden" den Aufruf des Constructors, bzw. das Erstellen der Klasse verbieten.
- Es gibt einen privaten constructor, welcher nur innerhalb der eigenen Unit vom eigenem Code aufgerufen werden kann.
- Und es gibt einen public Constructor, in dem nichts Anderes gemacht wird, außer eine Exceptions "nee, DU nicht!" zu werfen.

Captnemo 22. Jul 2014 17:55

AW: Constructor in der Vererbung
 
Na klar :-)

Unit BABClasses:
Delphi-Quellcode:
unit BABclasses;

interface

uses System.Contnrs, System.Classes, System.SysUtils;

type
  TBefehl = class(TObject)
  private
    fGuid: AnsiString;
  published
    property Guid: AnsiString read fGuid write fGuid;
  public
    constructor Create;
  end;

type
  TECHO = class(TBefehl)
  private
    fText: AnsiString;
  published
    property Text: AnsiString read fText write fText;
  end;

implementation

constructor TBefehl.Create;
var
  g: TGUID;
begin
  inherited Create;
  CreateGUID(g);
  self.fGuid:=GUIDToString(g);
end;

end.
Unit Main:
Delphi-Quellcode:
unit main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls,
  Vcl.Buttons, BABclasses, Vcl.Menus, System.Contnrs;

type
  Tfrm_batchdefinition = class(TForm)
    lst_test: TListBox;
    btn_Add: TSpeedButton;
    procedure btn_AddClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    BefehlsListe: TObjectList;
    { Public-Deklarationen }
  end;

var
  frm_main: Tfrm_main;

implementation

{$R *.dfm}

procedure Tfrm_main.FormCreate(Sender: TObject);
begin
  Befehlsliste:=TObjectList.Create;
end;

procedure Tfrm_main.FormDestroy(Sender: TObject);
begin
  Befehlsliste.free;
end;

procedure Tfrm_main.btn_AddClick(Sender: TObject);
var
  echo: TECHO;
begin
  echo:=TECHO.Create;
  lst_temp.items.add(echo.guid);
end;
Ich habs aber mal zusammengekürzt, kann nicht alles hier posten. So aber in etwa. Hier habe ich das Create in Public, und so geht's auch.

himitsu 22. Jul 2014 19:00

AW: Constructor in der Vererbung
 
Wie wäre es, wenn die TObjectList auch noch auf OwnsObject umgestellt wird?

Oder man verwendet stattdessen eine generische TObjektList<TBefehl>, oder eine TCollection, bzw. eine TQueue<TBefehl> usw., welche sich auch um die Freigabe der Objekte kümmern.

Statt die Objekte manuell zu erstellen, kann man der Liste auch Funktionen verpassen, welche diese Instanzen intern erstellen und zu sich hinzufügen.

Stevie 23. Jul 2014 10:44

AW: Constructor in der Vererbung
 
Zitat:

Zitat von Captnemo (Beitrag 1266359)
Aber: Constructor in Public....schon geht's. Gut, dann hab ich das vielleicht nicht ganz richtig verstanden.
Wenn ich mein Object TECHO in der gleichen Unit erzeugt hätte, hätte es vielleicht auch so funktioniert.

In der Unit Main ist der Konstruktor von TBefehl nicht sichtbar, wenn er private ist. Daher bleibt dort nur die Möglichkeit, den Konstruktor von TObject aufzurufen, der ja immer verfügbar ist. Auch wenn, TEcho in derselben Unit gewesen wäre, hätte das nichts geändert. Denn auch dann wäre der Konstruktor nicht sichtbar gewesen, da er ja Teil von TBefehl ist.

Private sollte man grundsätzlich nur Member machen, die nur innerhalb dieser Klasse benutzt werden.

himitsu 23. Jul 2014 11:12

AW: Constructor in der Vererbung
 
Nicht ganz. Innerhalb einer Unit ist Private wie Public, weswegen auch das Strict Private erfunden wurde.

Uwe Raabe 23. Jul 2014 11:58

AW: Constructor in der Vererbung
 
Zitat:

Zitat von himitsu (Beitrag 1266410)
Nicht ganz. Innerhalb einer Unit ist Private wie Public, weswegen auch das Strict Private erfunden wurde.

Das schränkt Stevie's Empfehlung ja nicht ein.

Man kann das Verhalten von Private innerhalb einer Unit auch als Designfehler betrachten, der aus Kompatibilitätsgründen nicht direkt behoben werden konnte. Die Einführung von Strict Private als echtes Private war sozusagen die Konsequenz.

Wenn ich sehe, daß auf Private Member von außerhalb der eigenen Klasse zugegriffen wird, ist das eigentlich ein Fall für ein Refactoring. Leider ist das auch im Delphi-eigenen Source viel zu weit verbreitet.

Stevie 23. Jul 2014 12:19

AW: Constructor in der Vererbung
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1266412)
Man kann das Verhalten von Private innerhalb einer Unit auch als Designfehler betrachten, der aus Kompatibilitätsgründen nicht direkt behoben werden konnte. Die Einführung von Strict Private als echtes Private war sozusagen die Konsequenz.

Wenn ich sehe, daß auf Private Member von außerhalb der eigenen Klasse zugegriffen wird, ist das eigentlich ein Fall für ein Refactoring. Leider ist das auch im Delphi-eigenen Source viel zu weit verbreitet.

Da stimme ich bedingt zu. Es gibt teilweise schon Dinge, die innerhalb der Bibliothek selber zugreifbar sein müssen aber nicht für einen Benutzer dieser Bibliothek. Dazu gibt es zum Beispiel in C# die Sichtbarkeit internal.
In Delphi bleibt einem da leider nichts anderes übrig, als die beteiligten Klassen in dieselbe Unit zu packen und dort auf private oder protected Member zuzugreifen.

Mikkey 23. Jul 2014 12:39

AW: Constructor in der Vererbung
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1266412)
Man kann das Verhalten von Private innerhalb einer Unit auch als Designfehler betrachten, der aus Kompatibilitätsgründen nicht direkt behoben werden konnte.

Man kann nicht nur, man muss, denn das Verhalten vom "protected" ist in dieser Hinsicht genau dasselbe (zumindest im Delphi7):

Delphi-Quellcode:
type
  TForm1 = class(TForm)
  protected
    Procedure ProtectedProc();
  end;
var
  Form1: TForm1;

implementation

Procedure TForm1.ProtectedProc();
begin
end;

procedure Main();
begin
  Form1.ProtectedProc(); // kein Fehler !!!
end;
Edit - Anmerkung: Ich habe nur das von der IDE vorgefertigte Projekt ergänzt und gekürzt. Das es als Programm so nicht laufen kann, ist mir klar.

DeddyH 23. Jul 2014 12:45

AW: Constructor in der Vererbung
 
Das stimmt nicht ganz. Abgeleitete Klassen können auf private Felder/Methoden nur dann zugreifen, wenn sie in derselben Unit deklariert sind. Für protected Members gilt das nicht.

Der schöne Günther 23. Jul 2014 12:49

AW: Constructor in der Vererbung
 
Dann wären da noch Tricksereien mit Klassenhelfern und Interposer-Klassen (die ja grade wegen dem "oder selbe Unit"-Prinzip funktionieren).

Die Sichtbarkeitsmodifikatoren einmal überarbeiten- Das ist mein #1-Feature was mir in der Sprache Delphi/Object Pascal am meisten fehlt. Bitte gebt mir vernünftige Namespaces und einen Sichtbarkeitsmodifikator der dem "internal" in C# bzw dem "Default" in Java entspricht. Und damit bitte gleich ein
Delphi-Quellcode:
uses System.Generics.*
8-)

C++ hat zwar auch nur public, private und protected aber wenigstens noch vernünftige Namespaces und im Notfall ein "using". In Pascal gewinnt einfach nur der letzte Import- Und man bekommt noch nicht einmal eine Compilerwarnung wenn eine Unit Symbole einer anderen einfach verdeckt. Das finde ich schon ziemlich hart.

Captnemo 23. Jul 2014 17:22

AW: Constructor in der Vererbung
 
Ich muss das ursprüngliche Thema noch mal aufgreifen, da ich noch ein paar Probleme habe, die ich mir nicht erklären kann.

Hier noch mal meine Klassen:
Delphi-Quellcode:
type
  TBefehl = class(TObject)
  private
    fGuid: AnsiString;
    fZeilen: Integer;
    fZeilenText: AnsiString;
    fFirstRow: Integer;
    fLastRow: Integer;
    fElseRow: Integer;
    FBlockGuid: AnsiString;
    FIsInBlock: Boolean;
    FIsInElse: Boolean;
  published
    property Zeilen: Integer read fZeilen write fZeilen;
    property ZeilenText: AnsiString read fZeilenText write fZeilenText;
    property FirstRow: Integer read fFirstRow write fFirstRow;
    property LastRow: Integer read fLastRow write fLastRow;
    property ElseRow: Integer read fElseRow write fElseRow;
    property IsInBlock: Boolean read FIsInBlock write FIsInBlock;
    property IsInElse: Boolean read FIsInElse write FIsInElse;
    property GUID: AnsiString read fGuid write fGuid;
    property BlockGuid: AnsiString read FBlockGuid write FBlockGuid;
  public
    constructor Create;
    procedure WriteBefehlToStream(Stream: TStream);
    procedure ReadBefehlFromStream(Stream: TStream);
  end;

type
  TECHO = class(TBefehl)
  private
    fOn: Boolean;
    fOff: Boolean;
    fAt: Boolean;
    fText: AnsiString;
    procedure SetRow(Row: Integer);
    function GetRow: Integer;
    procedure SetIsInBlock(Value: Boolean);
    function GetIsInBlock: Boolean;
    procedure SetIsInElse(Value: Boolean);
    function GetIsInElse: Boolean;
    procedure SetBlockGuid(Value: Ansistring);
    function GetBlockGuid: Ansistring;
  public
    procedure BuildZeilenText;
    procedure WriteToStream(Stream: TStream);
    procedure ReadFromStream(Stream: TStream);
  published
    property IsOn: Boolean read fOn write fOn;
    property IsOff: Boolean read fOff write fOff;
    property IsAt: Boolean read fAt write fAt;
    property Text: AnsiString read fText write fText;
    property Row: Integer read GetRow write SetRow;
    property localIsInBlock: Boolean read GetIsInBlock write SetIsInBlock;
    property localIsInElse: Boolean read GetIsInElse write SetIsInElse;
    property localBlockGuid: AnsiString read GetBlockGuid write SetBlockGuid;
  end;
Die Funktionen dazu
Delphi-Quellcode:
procedure TECHO.SetRow(Row: Integer);
begin
  fFirstRow:=Row;
  fLastRow:=Row;
  fElseRow:=-1;
  fIsInBlock:=False;
  fIsInElse:=False;
  fBlockGuid:='';
end;

function TECHO.GetRow: Integer;
begin
  Result:=fFirstRow;
end;

procedure TECHO.SetIsInBlock(Value: Boolean);
begin
  self.FIsInBlock:=Value;
end;

function TECHO.GetIsInBlock: Boolean;
begin
  Result:=Self.FIsInBlock;
end;

procedure TECHO.SetIsInElse(Value: Boolean);
begin
  self.FIsInElse:=Value;
end;

function TECHO.GetIsInElse: Boolean;
begin
  Result:=self.FIsInElse;
end;

procedure TECHO.SetBlockGuid(Value: AnsiString);
begin
  self.FBlockGuid:=Value;
end;

function TECHO.GetBlockGuid: AnsiString;
begin
  Result:=self.FBlockGuid;
end;

procedure TECHO.BuildZeilenText;
begin
  if (Self.fOn and not Self.fOff) then
    Self.ZeilenText:='ECHO On' else
  if (not Self.fOn and Self.fOff) then
    Self.ZeilenText:='ECHO Off' else
  if (not Self.fOn and not Self.fOff) then
    Self.ZeilenText:='ECHO '+self.fText;
  if self.fAt then
    Self.ZeilenText:='@'+Self.ZeilenText;
  self.Zeilen:=1;
end;
in der Formunit geht's dann folgendermaßen:

Delphi-Quellcode:
    echo:=TECHO.Create;
    echo.IsOn:=frm_echo.rb_echoon.Checked;
    echo.IsOff:=frm_echo.rb_echooff.Checked;
    echo.IsAt:=frm_echo.chk_at.Checked;
    echo.Text:=frm_echo.edt_eigenerText.Text;
    echo.BuildZeilenText;
    if (chk_anfuegen.Checked) or (lst_batch.Items.Count=0) then begin
      BefehlsListe.Add(echo);
      ActRow:=BefehlsListe.Count-1;
    end else begin
      begin
      echo.localIsInBlock:=InBlock;
      echo.localIsInElse:=InElse;
      echo.localBlockGuid:=Blockguid;
      BefehlsListe.Insert(IndexOfBefehlGuid(BefehlguidListe[ActRow]), echo);
BefehlsList ist eine TObjectList.

Nun mein Problem:

Bis zum "BefehlsListe.Insert" funktioniert alles. D.h. alle Properties werden wie gewünscht gesetzt, das habe ich im Debugger kontrolliert.

Wenn ich mir nach dem Insert mein Object "echo" im Debugger ansehe, dann die die Properties "IsInBlock, IsInElse und Blockguid" immer auf False bzw '' , egal was ich vorher gesetzt habe. Ich komme aber nicht dahinter, warum das so ist. Interessanterweise ist der Zeilentext, der ja auch in der Klasse TBefehl deklariert ist, noch dem Insert noch vorhanden.

Bitte nicht über den Sinn der Funktionen oder Propertynamen diskutieren, ist eh nur ein Spass- und Übungsprojekt.

Uwe Raabe 23. Jul 2014 17:37

AW: Constructor in der Vererbung
 
Wird vielleicht SetRow aufgerufen?

Captnemo 23. Jul 2014 17:52

AW: Constructor in der Vererbung
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1266465)
Wird vielleicht SetRow aufgerufen?

Daaaaaaaaankeee.
(Typisch Uwe...kurz und knapp und immer auf den Punkt;-) )

Das sucht man Stundenlang, und hat's vor Augen.....(kopfschütteln).
Hab schon in den Tisch gebissen, weil ich immer nach einem Fehler in der Definition gesucht habe.
Ich mach besser mal Schluss für heute :-D

Der schöne Günther 23. Jul 2014 18:13

AW: Constructor in der Vererbung
 
Am Rande: Dein BefehlsList ist noch die alte TObjectList und nicht die aus System.Generics.Collections, oder?

Auch: Kannst du nicht im Debugger weiter hineingehen (F7), was darin jetzt mit deinem Objekt angestellt wird?

Und: Ich habe jetzt nicht lange darüber gebrütet, aber GUID, Methoden zum Index suchen, usw- Hast du dir mal das TDictionary angeschaut?

Stevie 23. Jul 2014 21:41

AW: Constructor in der Vererbung
 
Zitat:

Zitat von Captnemo (Beitrag 1266468)
Das sucht man Stundenlang, und hat's vor Augen.....(kopfschütteln).
Hab schon in den Tisch gebissen, weil ich immer nach einem Fehler in der Definition gesucht habe.
Ich mach besser mal Schluss für heute :-D

Kleiner Tip fürs nächste mal: Data Breakpoints
Einfach auf ein Feld setzen, was sich wundersamerweise verändert hat und wuppdi stoppt der Debugger an der Stelle, wo das passiert.


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