2009-08-05 8 views
4

Considérons une interface commeorientation objet et sérialisation

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

et plusieurs classes qui mettent en œuvre l'interface:

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

J'ai une classe qui a une liste de IMyInterface implementors:

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

Maintenant à la question: comment puis-je charger la classe principale et en particulier la liste des éléments dans un objet orienté manière ed? Avant de pouvoir appeler la méthode de chargement virtuelle pour les éléments, je dois les créer et donc connaître leur type. Dans mon implémentation actuelle je stocke le nombre d'articles et pour chaque élément

  • un identificateur de type (IMyInterface obtient une fonction GetID supplémentaire)
  • appel à la méthode Enregistrer de l'élément

Mais ce signifie que pendant le chargement que je dois faire quelque chose comme

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

Mais cela ne semble pas être orienté objet très que je dois jouer du violon avec le code existant tout temps, j'ajoute un nouvel implémenteur. Y a-t-il une meilleure façon de gérer cette situation?

+0

Delphi. Il me surprend chaque fois que je vois du code Delphi dans les forums/forums/sites actuels. Je n'avais pas réalisé qu'il était encore utilisé autant. –

+4

@jeyoung: Delphi est et reste l'option ** BEST ** et la plus productive actuellement disponible pour l'écriture de code Windows 32 bits natif - pourquoi êtes-vous si surpris? –

Répondre

4

Une solution serait de mettre en œuvre une usine où toutes les classes s'enregistrent avec un ID unique.

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; 

Avantages

  • Vous pouvez supprimer la logique conditionnelle de votre méthode de chargement en cours.
  • Un emplacement pour rechercher les ID en double ou manquants.
+0

+1 Merci pour la réponse! D'où la méthode Register serait-elle appelée? De la section d'initialisation? Cela ne ferait que déplacer le code que je dois changer. Votre deuxième avantage est vrai cependant. Ne vous méprenez pas: bonne solution, mais en fonction de l'endroit où appeler Register, toujours pas parfait. – jpfollenius

+1

Je laisserais l'unité d'usine connaître toutes les unités d'implémentation et enregistrer chaque classe d'implémentation dans le constructeur de l'usine. Quand et où vous construisez l'usine serait alors à vous. Cela faciliterait l'échange de l'usine avec une autre usine de test pour vos testcases (vous avez testé ce test, n'est-ce pas?). L'inconvénient d'une telle solution est qu'elle permet d'abstraire tout jusqu'à un point où il devient difficile de savoir comment le Loader et les implémenteurs sont collés ensemble. –

2

Vous avez besoin d'un registre de classes dans lequel vous stockez chaque référence de classe avec leur ID unique. Les classes s'enregistrent dans la section d'initialisation de leur unité.

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: déplacé le registre de classe dans une catégorie distincte:

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); 

Cela devrait vous indiquer dans la direction, pour une très bonne introduction dans ces méthodes lisent le célèbre Martin Fowler article, esp. la section à propos de Interface Injection

+0

"Les classes s'enregistrent dans la section d'initialisation de leur unité" ... c'est une mauvaise idée à mon humble avis. Pourquoi les classes d'implémentation devraient-elles connaître l'existence de TMainClass? – jpfollenius

+1

@Smasher: Cela ne suit pas, le design dans la réponse est juste malheureux. Le registre de classe doit résider en dehors de TMainClass, peut-être là où les interfaces sont déclarées. Vous pouvez ajouter autant de niveaux d'indirection que vous le souhaitez. Cependant, laisser les classes d'implémentation s'inscrire elles-mêmes avec un registre global a du sens, car tout fonctionne simplement en ajoutant et en supprimant les unités des classes d'implémentation. – mghie

+0

Exactement. Merci mghie, d'avoir expliqué mon point, dont l'essence est la liaison ** runtime ** de TTImplementingClassX et de TMainClass. – Ozan

Questions connexes