| Autor |
Beitrag |
Mossi
      
Beiträge: 99
Erhaltene Danke: 2
Win XP, Linux
D7 Enterprise, K3 Prof
|
Verfasst: Mi 11.06.08 10:34
ich hab folgendes Problem. Oder nennen wir es Frage.
Ich hab eine Bases-Klasse TImport. Diese stellt bietet alle Methoden an, die man für eine Import-Aktion benötigt. Letztendlich gibt es dann noch den abstrakten Header der Methode 'ReadData'. Die Implementierung dieser Klasse geschieht in abgeleiteten Klassen. Ich muss also abgeleitete Klassen verwenden um das Objekt 'vollständig' zu machen.
Die abgeleiteten Klassen sind von einzelnen Kunden abhängig. So gibt es zum Beispiel eine Klasse TImportKundeA (TImport) und eine Klasse TImportKundeB (TImport). Diese abgeleiteten Klassen beinhalten nur die Methode ReadData.
Das Problem kommt eigentich jetzt erst mit der Steuerung des Aufrufs. Derzeit hab ich es so gelöst:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| var import: TImport; begin if FSettings.Company = 'KundeA' then import := TImportKundeA.Create else if FSettings.Comapny = 'KundeB' then import := TImportKundeB.Create;
import.ReadData; end; |
Das funktioniert auch wunderbar. Allerdings finde ich es etwas umständlich, dass ich immer wieder in die Steuerung eingreifen muss, wenn ein neuer Kunde hinzukommt. Schöner würde ich es finden, wenn der Klassenname dynamisch bestimmt werden könnte abhängig vom Inhalt von FSetting.Company.
Also es sollte dymisch vom Inhalt dieser Property ein Klassenname erstellt werden und dieser augerufen werden. In der Property Company steht ja genau der Name drin, der auch in der Unterklasse angehängt wird. Also rein von den Namen her könnte ich den richtigen Klassennamen rausfinden. Aber wie ruf ich einen solchen Klassennamen - den ich ja dann nur als String hab auf?
Ist so etwas möglich? Und wenn ja, wie macht man so etwas?
|
|
jaenicke
      
Beiträge: 19346
Erhaltene Danke: 1754
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 11.06.08 11:00
Dafür kannst du eine Factory-Klasse benutzen, die dir dann die entsprechende Klasse erzeugt. Die Klassen, die erzeugt werden können, können sich dort z.B. registrieren und legen dabei einen Namen und einen Pointer auf die entsprechende Erzeugungsroutine ab.
Alternativ kannst du natürlich auch alle Klassen dort manuell eintragen, je nach Projekt.
Hilfreich dabei sind z.B. class methods, die ohne die Klasse vorher zu erzeugen aufgerufen werden können.
Beschrieben ist ein solches Pattern z.B. bei Wikipedia:
en.wikipedia.org/wik...ctory_method_pattern
Wie genau du das umsetzt ist abhängig davon wie das bei dir genau aussieht, d.h. wann wo bekannt ist, welche Klassen es gibt, ob sich das z.B. via Plugin erweitern lassen soll (dann ist die oben erwähnte Registrierung bei der Factoryklasse sinnvoll), etc.
|
|
Mossi 
      
Beiträge: 99
Erhaltene Danke: 2
Win XP, Linux
D7 Enterprise, K3 Prof
|
Verfasst: Mi 11.06.08 12:29
Danke. Der Tip mit den Design Pattern war schon mal ganz gut. Hab davon schon mal was gehört, aber in der Hinsicht hab ich in Delphi relativ wenig Erfahrung.
Ich hab jetzt folgendes implementiert:
In der BasisKlasse TImport hab ich eine class function als Factory eingebaut
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| class function TImport.ClassFactory(ACompany: string; AOrderStrings: TStringList; AComponents: TComponents; ASettings: TSettings): TImport; var import: TImport; begin import := TImport(FindClass('TImport' + ACompany)); ClassFactory := import.Create(AOrderStrings, AComponents, ASettings); end; |
Damit FindClass funktioniert, hab ich die Klassen in den jeweiligen Units registriert:
Ganz am Ende in den abgeleiteten Klassen. Ich bin mir da nicht sicher, wo der initialization Teil genau hingehört, aber so hat dann FindClass zumindest was gefunden.
Delphi-Quelltext 1: 2: 3: 4:
| initialization RegisterClass(TImportARJ);
end. |
Damit RegisterClass überhaupt funktioniert, hab ich die Basisklasse nicht von TObject abgeleitet, sondern von TComponent.
Im Hauptprogramm möchte ich dann die jeweilige Klasse aufrufen:
Delphi-Quelltext 1:
| import := TImport.ClassFactory(FSettings.Company, OrderStrings, FComponents, FSettings); |
Das funktioniert aber alles nicht. Der Aufruf der ClassFactory im allgemeinen klappt. FindClass liefert eine Klasse zurück, aber die ist eigentlich nur Schrott, wenn ich sie im Debugger anschau. Man sieht nur kryptische Zeichen dann. Und beim Create zerlegt's mich dann mit einer Access Violation.
Muss ich das FindClass irgendwie anders verwenden? Oder liegt mein Fehler ganz wo anders. Vom Aufbau her wär es jetzt ungefähr das, was ich mir vorstelle.
|
|
jaenicke
      
Beiträge: 19346
Erhaltene Danke: 1754
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 11.06.08 12:44
Also ich meinte jetzt eine eigene Registriermethode in deiner Factory-Klasse, die den Namen als String und einen Zeiger auf eine Erzeugungsroutine (wieder class method) bekommt. Mit RegisterClass und Klassen via FindClass habe ich noch nicht gearbeitet, keine Ahnung, inwiefern ich mir manches auch hätte sparen können  .
|
|
Mossi 
      
Beiträge: 99
Erhaltene Danke: 2
Win XP, Linux
D7 Enterprise, K3 Prof
|
Verfasst: Mi 11.06.08 12:56
Das Problem hat sich soeben erledigt. Hab noch ein paar Codefragmente im Internet gefunden und hab es jetzt doch noch geschafft, was daraus zu basteln.
Die class function sieht jetzt so aus:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| class function TImport.ClassFactory(ACompany: string; AOrderStrings: TStringList; AComponents: TComponents; ASettings: TSettings): TImport; var cls: TPersistentClass; obj: TObject; begin cls := FindClass('TImport' + ACompany); obj := cls.Create; TImport(obj).FOrderStrings := AOrderStrings; TImport(obj).FComponents := AComponents; TImport(obj).FSettings := ASettings; ClassFactory := TImport(obj); end; |
Bisschen umständlich, aber so bekomme ich meine gewünschte Klasse zurück. Und meinen Constructor, den ich in der Basisklasse hatte, hab ich damit auch verworfen. Da der aber eh nur die Zuweisungen enthielt, ist das kein Problem.
Der Aufruf sieht dann so aus:
Delphi-Quelltext 1:
| import := TImport.ClassFactory(FSettings.Company, TStringList.Create, FComponents, FSettings); |
Und noch eine kleine Änderung. Mir hat es nämlich nicht gefallen, dass ich die Basisklasse von TComponent ableite und hab sie deswegen einfach von TPersistent abgeleitet. Über diese Umwege klappt das jetzt also.
Kurz noch zur Erklärung: Ich wollte das so machen, damit ich für einen neuen Kunden lediglich eine neue Unit schreiben muss und nichts anderes anfassen im Hauptprogramm anfassen muss. In absehbarer Zeit will ich das noch so weit fortführen, dass diese kundenspezifischen Klassen in eigene DLLs ausgelagert werden. Aber eben alles Schritt für Schritt.
|
|
Tilman
      
Beiträge: 1405
Erhaltene Danke: 51
Win 7, Android
Turbo Delphi, Eclipse
|
Verfasst: Mo 04.01.10 15:14
Der Thread ist zwar recht alt, da ich aber gerade selbst an einem solchen Problemchen saß will ich euch die Lösung nicht vorenthalten. Zunächst mal muss die Klasse die wir ansprechen wollen von TPersistent abgeleitet und Registriert (RegisterClass) sein, wie hier ja auch schon angesprochen wurde.
| Zitat: |
Delphi-Quelltext 1:
| import := TImport(FindClass('TImport' + ACompany)); |
|
Hier liegt der Hase sozusagen begraben. Das Typecasten einer Klasse mittels - in diesem Beispiel - TImport, aber genauso TButton, TEdit o.ä. funktioniert nicht. Das Liegt daran weil derartige Typecasts einen Zeiger auf ein existierendes Objekt, und nicht auf eine Klasse erwarten, aber das Objekt haben wir erst nach dem Aufruf von Create. Wie also Typecasten? Die Lösung steckt in Klassentypen. Je nach dem wie unsere Klasse deklariert ist kann man TPersistentClass, TControlClass oder ähnliches verwenden. Im Obigen Beispiel müsste man sich einen eigenen definieren:
Delphi-Quelltext 1: 2: 3: 4: 5:
| type TImportClass = class of TImport;
[..]
import := TImportClass(FindClass('TImport' + ACompany)); |
und dann kann man auch seinen eigenen Konstruktor endlich aufrufen.
_________________ Bringe einen Menschen zum grübeln, dann kannst du heimlich seinen Reis essen.
(Koreanisches Sprichwort)
|
|
|