Autor Beitrag
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Mi 04.05.05 18:16 
Kindklassen der Mutterklasse bekannt machen

Es mag Situationen geben, in denen eine Mutterklasse wissen muss, welche Kindklasse von ihr abgeleitet worden sind. Stellen wir uns z.B. ein Programm vor, welches bestimmte Messwerte einlesen und auswerten soll. Dabei werden je nach Einsatzgebiet verschiedene Messwerte benötigt, viele Grundfunktionen sind aber für alle Einsatzorte identisch.

Also erstellt man eine Mutterklasse mit allen Methoden, von denen einige abstrakt bleiben, weil ihnen erst in den Kindklassen (für jeden Einsatzort eine) Leben eingehaucht wird.

ausblenden mutter.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
TMutter = class(TObject)
private
public
  //gemeinsame Methoden ...
  procedure doSomethingCommon;
  //...

  //abstrakte Methoden
  procedure auswertung; virtualabstract;
  //...
end;


ausblenden kind1.pas
1:
2:
3:
4:
5:
6:
TKind1 = class(TMutter)
private
public
  procedure auswertung; override;
  //...
end;


ausblenden kind2.pas
1:
2:
3:
4:
5:
6:
TKind2 = class(TMutter)
private
public
  procedure auswertung; override;
  //...
end;


Im Programm deklariert man die Mutterklasse und instanziert je nach Einsatzgebiet (vom Nutzer angegeben) eine der Kindklassen. Probleme bekommt man, wenn man eine Funktion "aus Datei laden" einbauen will. Denn die Datei wird je nach verwendeter Kindklasse eine andere Struktur haben, da ja andere Daten verwendet werden. Wie kann man also diese Datei möglichst elegant laden?

Die erste Idee wäre folgendes:
ausblenden mutter.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
type
  TIDString = String[3];  //speichert eine ID, die jede Klasse eindeutig identifiziert

TMutter = class(TObject)
protected
  //Wird in jeder Kindklasse implementiert, um die klassenspezifischen Daten von einem Stream zu holen
  procedure readDateFromStream(fs : TFileStream); virtualabstract;
public
  //... alle anderen Methoden ...
  class function loadFromFile(filename : String) : TMutter;
end;

//...

class function TMutter.loadFromFile(filename : String) : TMutter;
var
  fs : TFileStream;
  ID : TIDString;
begin
  fs := TFileStream.Create(filename, fmOpenRead);
  try
    fs.Read(ID, sizeOf(TIDString));
    if ID = 'KI1' then result := TKind1.Create;
    if ID = 'KI2' then result := TKind2.Create;
    //...
    result.readDataFromStream(fs);
  finally
    fs.Free;
  end;
end;


Der Nachteil ist offensichtlich: Jedes Mal, wenn wir eine neue Kindklasse einführen, müssen wir auch die Mutterklasse abändern. Das widerspricht natürlich den Prinzipien der OOP und ist außerdem sehr unpraktisch. Einfacher geht es, wenn jede Kindklasse sich bei der Mutterklasse "anmeldet". Dazu führen wir ein globales (ja, dieses Mal muss es sein ;-)) Array ein, welches aber nur in der Datei mutter.pas sichtbar ist. Angesprochen wird es über eine Prozedur:
ausblenden mutter.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
TMutter = class(TObject)
//...
public
  //... alle anderen Methoden ...
  class function getID : TIDString; virtualabstract;
end;
  
type
  TKindClass = class of TMutter;

procedure registriereKindklasse(klasse : TKindClass);

implementation

var
  KIND_KLASSEN : Array of TKindClass;

procedure registriereKindklasse(klasse : TKindClass);
begin
  SetLength(KIND_KLASSEN, Length(KIND_KLASSEN)+1);
  KIND_KLASSEN[High(KIND_KLASSEN)] := klasse;
end;


Und in jeder Unit, in der wir eine Kindklasse einführen, muss sich die Kindklasse in das Array eintragen und außerdem so erweitert werden, dass sie ihre ID zurückgibt:
ausblenden kind1.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
TKind1 = class(TMutter)
private
public
  //...
  class function getID : TIDString; override;
end;
  
//...

class function TKind1.getID : TIDString;
begin
  result := 'KI1';
end;

//...

initialization
  registriereKindklasse(TKind1)


ausblenden kind2.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
TKind2 = class(TMutter)
private
public
  //...
  class function getID : TIDString; override;
end;
  
//...

class function TKind2.getID : TIDString;
begin
  result := 'KI2';
end;

//...

initialization
  registriereKindklasse(TKind2)


Die Methode zum Laden einer Datei sieht dann so aus:
ausblenden mutter.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
class function TMutter.loadFromFile(filename : String) : TMutter;
var
  fs : TFileStream;
  ID : TIDString;
  i  : Integer;
begin
  fs := TFileStream.Create(filename, fmOpenRead);
  try
    fs.Read(ID, sizeOf(TIDString));
    for i:=0 to High(KIND_KLASSEN) do
      if KIND_KLASSEN[i].getID = ID then
      begin
        result := KIND_KLASSEN[i].Create;
        break;
      end;
    result.readDataFromStream(fs);
  finally
    fs.Free;
  end;
end;


Somit muss die Mutterklasse nie wieder geändert werden und der Aufwand in den Kindklassen ist ebenfalls minimal.

Ich habe dieses zwar anhand eines (relativ) konkreten Beispiels gezeigt, jedoch glaube ich, dass es sehr viel mehr Situationen gibt, in denen man eine Art "Registrierung" von Klassen gebrauchen kann.

Ich hoffe, es hilft irgendwann mal jemanden. :-)

Vielen Dank an user profile iconMotzi, der als Erster ein (damals noch statisches) Array von Klassen vorschlug. Danke!

Viele Grüße
Christian

P.S.: Diesen Artikel gibt es auch als HTML-Dokument auf meiner Homepage
Moderiert von user profile iconjasocul: Beitrag geprüft am 14.06.2006

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".


Zuletzt bearbeitet von Christian S. am Mi 04.05.05 19:09, insgesamt 1-mal bearbeitet