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.
mutter.pas
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| TMutter = class(TObject) private public procedure doSomethingCommon; procedure auswertung; virtual; abstract; end; |
kind1.pas
1: 2: 3: 4: 5: 6:
| TKind1 = class(TMutter) private public procedure auswertung; override; end; |
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:
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]; TMutter = class(TObject) protected procedure readDateFromStream(fs : TFileStream); virtual; abstract; public 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:
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 class function getID : TIDString; virtual; abstract; 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:
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) |
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:
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
Motzi, 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 jasocul: Beitrag geprüft am 14.06.2006
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".