2009-08-05 11 views
4

Betrachten Sie eine Schnittstelle wieObjektorientierung und Serialisierung

IMyInterface = interface 
    procedure DoSomethingRelevant; 
    procedure Load (Stream : TStream); 
    procedure Save (Stream : TStream); 
end; 

und mehrere Klassen, die die Schnittstelle implementieren:

TImplementingClass1 = class (TInterfacedObject, IMyInterface) 
    ... 
end; 
TImplementingClass2 = class (TInterfacedObject, IMyInterface) 
    ... 
end; 
... 

Ich habe eine Klasse, die eine Liste von IMyInterface Implementierer hat:

TMainClass = class 
strict private 
    FItems : TList <IMyInterface>; 
public 
    procedure LoadFromFile (const FileName : String); 
    procedure SaveToFile (const FileName : String); 
end; 

Nun zur Frage: Wie kann ich die Hauptklasse und vor allem die Objektliste in ein Objekt-Orientierungssystem laden ed-Art? Bevor ich die virtuelle Load-Methode für die Elemente aufrufen kann, muss ich sie erstellen und muss daher deren Typ kennen. In meiner aktuellen Implementierung speichere ich die Anzahl der Elemente und dann für jedes Element

  • eine Typkennung (IMyInterface bekommt eine zusätzliche GetID Funktion)
  • Aufruf der Save-Methode des Elements

Aber die während des Ladens bedeutet, dass ich so etwas wie

ID := Reader.ReadInteger; 
case ID of 
    itClass1 : Item := TImplementingClass1.Create; 
    itClass2 : Item := TImplementingClass2.Create; 
    ... 
end; 
Item.Load (Stream); 

zu tun haben, aber das ist nicht zu sehr objektorientiert scheint, da ich mit vorhandenen Code Geige haben allen Zeit füge ich einen neuen Implementor hinzu. Gibt es einen besseren Weg, um mit dieser Situation umzugehen?

+0

Delphi. Es überrascht mich jedes Mal, wenn ich Delphi-Code in aktuellen Foren/Foren sehe. Ich wusste nicht, dass es noch so oft benutzt wurde. –

+4

@jeyoung: Delphi ist und bleibt die ** BESTE ** und produktivste Option, die derzeit zum Schreiben von nativem 32-Bit Windows-Code zur Verfügung steht - warum sind Sie so überrascht? –

Antwort

4

Eine Lösung wäre, eine Factory zu implementieren, in der alle Klassen sich mit einer eindeutigen ID registrieren.

TCustomClassFactory = class(TObject) 
public  
    procedure Register(AClass: TClass; ID: Integer); 
    function Create(const ID: Integer): IMyInterface; 
end; 

TProductionClassFactory = class(TCustomClassFactory) 
public 
    constructor Create; override; 
end; 

TTestcase1ClassFactory = class(TCustomClassFactory); 
public 
    constructor Create; override; 
end; 

var 
    //***** Set to TProductionClassFactory for you production code, 
    //  TTestcaseXFactory for testcases or pass a factory to your loader object. 
    GlobalClassFactory: TCustomClassFactory; 

implementation 

constructor TProductionClassFactory.Create; 
begin 
    inherited Create; 
    Register(TMyImplementingClass1, 1); 
    Register(TMyImplementingClass2, 2); 
end; 

constructor TTestcase1ClassFactory.Create; 
begin 
    inherited Create; 
    Register(TMyImplementingClass1, 1); 
    Register(TDoesNotImplementIMyInterface, 2); 
    Register(TDuplicateID, 1); 
    Register(TGap, 4); 
    ... 
end; 

Vorteile

  • Sie können die bedingte Logik von Ihrer aktuellen Lademethode entfernen.
  • Ein Ort, um nach doppelten oder fehlenden IDs zu suchen.
+0

+1 Danke für die Antwort! Woher soll die Register-Methode aufgerufen werden? Aus dem Initialisierungsabschnitt? Das würde nur den Code verschieben, den ich ändern muss. Ihr zweiter Vorteil ist jedoch wahr. Versteh mich nicht falsch: nette Lösung, aber je nachdem, wo Sie anrufen, immer noch nicht perfekt. – jpfollenius

+1

Ich würde die Fabrikeinheit über alle Implementierungseinheiten informieren und jede Implementierungsklasse im Konstruktor der Fabrik registrieren. Wann und wo Sie die Fabrik bauen, liegt dann an Ihnen. Dies würde es einfacher machen, die Fabrik mit einer anderen Testfabrik für Ihre Testfälle zu tauschen (Sie haben getestet, nicht wahr?). Der Nachteil einer solchen Lösung ist, dass sie alles bis zu einem Punkt abstrahiert, an dem es schwierig wird zu wissen, wie der Loader und die Implementoren zusammen geklebt sind. –

2

Sie benötigen eine Klassenregistrierung, in der Sie jede Klassenreferenz zusammen mit ihrer eindeutigen ID speichern. Die Klassen registrieren sich im Initialisierungsabschnitt ihrer Einheit.

TImplementingClass1 = class (TInterfacedObject, IMyInterface) 
    ... 
end; 
TImplementingClass2 = class (TInterfacedObject, IMyInterface) 
    ... 
end; 

TMainClass = class 
public 
    procedure LoadFromFile (const FileName : String); 
    procedure SaveToFile (const FileName : String); 
end; 

Edit: bewegt, um die Klasse Registrierung in eine separate Klasse:

TMyInterfaceContainer = class 
strict private 
class var 
    FItems : TList <IMyInterface>; 
    FIDs: TList<Integer>; 
public 
    class procedure RegisterClass(TClass, Integer); 
    class function GetMyInterface(ID: Integer): IMyInterface; 
end; 

procedure TMainClass.LoadFromFile (const FileName : String); 
    ... 
    ID := Reader.ReadInteger; 
    // case ID of 
    // itClass1 : Item := TImplementingClass1.Create; 
    // itClass2 : Item := TImplementingClass2.Create; 
    // ... 
    // end; 
    Item := TMyInterfaceContainer.GetMyInterface(ID); 
    Item.Load (Stream); 
    ... 

initialization 
    TMyInterfaceContainer.RegisterClass(TImplementingClass1, itClass1); 
    TMyInterfaceContainer.RegisterClass(TImplementingClass2, itClass2); 

Diese Sie in die Richtung zeigen sollte, für eine sehr gute Einführung in diese Methoden die berühmte Martin Fowler article lesen, esp. der Abschnitt über Interface Injection

+0

"Die Klassen registrieren sich im Initialisierungsabschnitt ihrer Einheit" ... das ist eine schlechte Idee, IMHO. Warum sollten die implementierenden Klassen von der Existenz von TMainClass wissen? – jpfollenius

+1

@Smasher: Dies folgt nicht, das Design in der Antwort ist nur bedauerlich. Die Klassenregistrierung sollte sich außerhalb der TMainClass befinden, möglicherweise wo die Schnittstellen deklariert sind. Sie können so viele Ebenen der Indirektion hinzufügen, wie Sie möchten. Es ist jedoch sinnvoll, die Implementierungsklassen bei einer globalen Registrierung registrieren zu lassen, da alles nur durch Hinzufügen und Entfernen der Einheiten von Implementierungsklassen funktioniert. – mghie

+0

Genau. Danke mghie, um meinen Punkt zu erklären, dessen Essenz die ** Laufzeit ** -Bindung von TTImplementingClassX und TMainClass ist. – Ozan