Klassentyp als Rückgabeparameter

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Klassentyp als Rückgabeparameter

Beitrag von Nimral »

Ich habe eine Hierarchie von Klassen, die alle einen gemeinsamen Vorgänger (TBaseClass) haben.

Je nach einer äußeren Bedingung muss ich eine von den abgeleiteten Klassen (TDerivedClass1, TDerivedClass2) instantiieren.

Im Moment mache ich das so:

Code: Alles auswählen

program project1;

{$mode ObjFPC}{$H+}
{$interfaces corba}

uses
  SysUtils;

type

  ITestInterface = interface
    procedure Identify;
  end;

  { TBaseClass }

  TBaseClass = class(ITestInterface)

  public
    procedure Identify; virtual; abstract;
  end;

  { TDerivedClass1 }

  TDerivedClass1 = class(TBaseClass)

  public
    procedure Identify; override;
  end;

  { TDerivedClass2 }

  TDerivedClass2 = class(TBaseClass)

  public
    procedure Identify; override;
  end;

  function GetClass(s: string): TBaseClass;

  begin
    Result := nil;
    if LeftStr(s, 1) = '1' then exit(TDerivedClass1.Create);
    if LeftStr(s, 1) = '2' then exit(TDerivedClass2.Create);
  end;

  procedure TDerivedClass1.Identify;
  begin
    Writeln('Hello from class 1');
  end;

  { TDerivedClass2 }

  procedure TDerivedClass2.Identify;

  begin
    Writeln('Hello from class 2');
  end;

var
  MyClass: TBaseClass;
  s: string;

begin
  MyClass := nil;
  repeat
    Write('Klasse: ');
    Readln(s);
    if s <> '' then
    begin
      if Assigned(MyClass) then
        FreeAndNil(MyClass);
      MyClass := GetClass(s);
      if assigned(MyClass) then
        MyClass.identify
      else
        writeln('Unknown class: ' + s);
    end;
  until s = '';
  if Assigned(MyClass) then
    FreeAndNil(MyClass);
end.
(1) An sich wollte ich GetClass anders haben, nämlich als Funktion, die den Typen der ausgewählten Klasse zurückbringt, und den zugehörigen create dann in der aufrufenden Routine ausführen. Ich habe nicht herausbekommen, ob und wenn ja wie das in Pascal möglich gewesen wäre.

(2) (Nur zur Kontrolle ob ich nicht einen OOP Denkfehler habe) An sich tut TBaseClass selber nichts, sondern definiert nur abstrakte Methoden, welche von den abgeleiteten Klassen dann zu erfüllen sind. Daher muss ich alle im Interface enthaltenen Member in jeder Klasse nochmal deklarieren und ggf implementieren. Interfaces sind also nichts anderes als eine Schablone, eine Merkhilfe, damit ich mich selber zwinge, alle betroffenen Klassen mit einer bestimmten Methode auszustatten. Daher habe ich TBaseClass mit dem Interface ITestInterface verbunden. Hätte ich GetClass auch irgendwie so schreiben können, dass ein Interface zurückkommt? Ich meine: nein, kann man so nicht lösen. Instantiieren kann man nur Klassen, wenn diese dann das entsprechende Interface implementieren kann man dieses gerne benützen über einen Typecast wie i := TTestInterface(myClass) und danach alles aufrufen was Teil von ITestInterface war (z.B. writeln(i.Identify)), aber instantiiert werden immer die Klassen.

Armin.

Benutzeravatar
theo
Beiträge: 10467
Registriert: Mo 11. Sep 2006, 19:01

Re: Klassentyp als Rückgabeparameter

Beitrag von theo »

Naja, es ist immer etwas schwierig zu antworten, wenn einem das Ziel nicht ganz klar ist.
Aber ich möchte dich mal auf Klassenreferenzen hinweisen.

Code: Alles auswählen

type
TComponentClass = class of TComponent; //TComponentClass ist schon vordefiniert, aber so kann man auch eigene Klassenreferenzen definieren.

function TForm1.CreateClass(AClass:TComponentClass):TComponent;
begin
  Result:=AClass.Create(self);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  CreateClass(TEdit);
  CreateClass(TLabel);
end;   
S.a. https://www.freepascal.org/docs-html/re ... 0-940006.1

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Klassentyp als Rückgabeparameter

Beitrag von wp_xyz »

Ich finde das ist ein schönes Beispiel, wie eine ungünstige Wahl der Bezeichner verhindert, in die richtige Richtung zu denken. Warum nennst du die Basis-Klasse TBaseClass mit dem "Class" am Ende? Ein TControl heißt ja auch nicht TControlClass. Wenn du aber in der Quelldatei controls.pas nachschaust, dann wirst du aber tatsächlich auch ein TControlClass finden, deklariert als "class of TControl". Das heißt: TControlClass umfasst alle Klassen, die von TControl abgeleitet sind. Und das ist, was du suchst:

Nenne die Basisklasse TBase (an Stelle von TBaseClass). Entsprechend benenne TDerivedClass1 zu TDerived1 und TDerivedClass2 zu TDerived2 um.
Führe den Klassentyp TBaseClass ein, als TBaseClass = class of TBase.
Nun kann deine Funktion GetClass den Klassentyp TBaseClass zurückliefern

Code: Alles auswählen

program Project1;

uses
  Classes, SysUtils;

type
  TBase = class(TObject)
  public
    procedure Identify; virtual; abstract;
  end;
  TBaseClass = class of TBase;
    
  TDerived1 = class(TBase)
  public
    procedure Identify; override;
  end;
  
  TDerived2 = class(TBase)
  public
    procedure Identify; override;
  end;
  
function GetClass(s: String): TBaseClass;
begin
  if s[1] = '1' then exit(TDerived1);
  if s[1] = '2' then exit(TDerived2);
  raise Exception.Create('Unbekannte Klasse angefordert.');
end;

procedure TDerived1.Identify;
begin
  WriteLn('Klasse TDerived1');
end;

procedure TDerived2.Identify;
begin
  WriteLn('Klasse TDerived2');
end;

var
  b: TBase;
begin
  b := GetClass('1').Create;
  b.Identify;
  b.Free;
  
  b := GetClass('2').Create;
  b.Identify;
  b.Free;
  
  ReadLn;
end.
Interfaces sind mir ziemlich suspekt, gerade bei selbstgeschriebenen Klassen kann man alles (?) mit Hilfe von virtuellen Methoden erledigen. Nur bei der Erweiterung bereits existierender Klassen führt manchmal kein Weg daran vorbei.

[EDIT]
theo war eine Minute schneller...

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Klassentyp als Rückgabeparameter

Beitrag von kupferstecher »

Nimral hat geschrieben:
Sa 12. Feb 2022, 08:09
(2) (Nur zur Kontrolle ob ich nicht einen OOP Denkfehler habe) An sich tut TBaseClass selber nichts, sondern definiert nur abstrakte Methoden, welche von den abgeleiteten Klassen dann zu erfüllen sind. Daher muss ich alle im Interface enthaltenen Member in jeder Klasse [...]
Bei Interfaces ist das so. Deine Benennung hört sich aber so an, als ob du eigentlich tatsächlich eine Klasse möchtest, da sind dann die Methoden von der Basisklasse schon vorhanden.

Also wie wp es gezeigt hat:

Code: Alles auswählen

  TBase = class
    [...]
  end;
    
  TDerived1 = class(TBase)
    [...]
  end;

Antworten