2009-07-08 8 views
6

Supposons que nous ayons un objet qui représente la configuration d'un composant matériel. Par souci d'argument, un contrôleur de température (TempController). Il contient une propriété, la température de consigne.La méthode Object-Oriented pour séparer le modèle de sa représentation

Je dois enregistrer cette configuration dans un fichier pour l'utiliser dans un autre périphérique. Le format de fichier (FormatA) est défini dans la pierre. Je ne veux pas que l'objet TempController soit au courant du format de fichier ... ce n'est pas pertinent pour cet objet. Donc je fais un autre objet, "FormatAExporter", qui transforme le TempController en la sortie désirée.

Un an plus tard, nous fabriquons un nouveau contrôleur de température, appelé "AdvancedTempController", qui a non seulement un point de consigne, mais aussi un contrôle de débit, ce qui signifie une ou deux propriétés supplémentaires. Un nouveau format de fichier est également inventé pour stocker ces propriétés ... appelons-le FormatB.

Les deux formats de fichiers sont capables de représenter les deux périphériques (supposons que AdvancedTempController a des valeurs par défaut raisonnables s'il manque de paramètres).

Voici donc le problème: Sans utiliser 'isa' ou une autre méthode de "triche" pour déterminer quel type d'objet j'ai, comment FormatBExporter peut-il gérer les deux cas? Mon premier instinct est d'avoir une méthode dans chaque contrôleur de température qui peut fournir un exportateur client pour cette classe, par exemple TempController.getExporter() et AdvancedTempController.getExporter(). Cela ne prend pas en charge plusieurs formats de fichiers.

La seule autre approche qui vient à l'esprit est d'avoir une méthode dans chaque contrôleur de température qui renvoie une liste de propriétés et leurs valeurs, puis le formateur peut décider comment les sortir. Ça marcherait, mais cela semble alambiqué.

MISE À JOUR: Lors d'autres travaux, cette dernière approche ne fonctionne pas vraiment bien. Si tous vos types sont simples, cela peut être le cas, mais si vos propriétés sont des objets, vous finissez par pousser le problème à un niveau inférieur ... vous êtes obligé de retourner une paire de valeurs String, Object, et l'exportateur devra savoir Les objets doivent en fait les utiliser. Cela pousse donc le problème à un autre niveau.

Y a-t-il des suggestions sur la façon dont je pourrais garder cette flexibilité?

Répondre

4

Ce que vous pouvez faire est laisser les TempControllers être responsables de la persistance en utilisant un archiveur générique.

class TempController 
{ 
    private Temperature _setPoint; 
    public Temperature SetPoint { get; set;} 

    public ImportFrom(Archive archive) 
    { 
     SetPoint = archive.Read("SetPoint"); 
    } 
    public ExportTo(Archive archive) 

    { 
     archive.Write("SetPoint", SetPoint); 
    } 
} 

class AdvancedTempController 
{ 
    private Temperature _setPoint; 
    private Rate _rateControl; 
    public Temperature SetPoint { get; set;} 
    public Rate RateControl { get; set;} 

    public ImportFrom(Archive archive) 
    { 
     SetPoint = archive.Read("SetPoint"); 
     RateControl = archive.ReadWithDefault("RateControl", Rate.Zero); 
    } 

    public ExportTo(Archive archive) 
    { 
     archive.Write("SetPoint", SetPoint); 
     archive.Write("RateControl", RateControl); 
    } 
} 

En gardant cette façon, les contrôleurs ne se soucient pas comment les valeurs réelles sont stockées, mais vous gardez toujours les entrailles de l'objet bien encapsulé.

Vous pouvez maintenant définir une classe d'archive abstraite que toutes les classes d'archives peuvent implémenter.

abstract class Archive 
{ 
    public abstract object Read(string key); 
    public abstract object ReadWithDefault(string key, object defaultValue); 
    public abstract void Write(string key); 
} 

FormatUn archiveur peut le faire dans un sens, et l'archive FormatB peut en faire un autre.

class FormatAArchive : Archive 
{ 
    public object Read(string key) 
    { 
     // read stuff 
    } 

    public object ReadWithDefault(string key, object defaultValue) 
    { 
     // if store contains key, read stuff 
     // else return default value 
    } 

    public void Write(string key) 
    { 
     // write stuff 
    } 
} 

class FormatBArchive : Archive 
{ 
    public object Read(string key) 
    { 
     // read stuff 
    } 

    public object ReadWithDefault(string key, object defaultValue) 
    { 
     // if store contains key, read stuff 
     // else return default value 
    } 

    public void Write(string key) 
    { 
     // write stuff 
    } 
} 

Vous pouvez ajouter un autre type de contrôleur et lui transmettre n'importe quel formateur. Vous pouvez également créer un autre formateur et le transmettre à n'importe quel contrôleur.

+0

Cela rend également le code plus testable car parce que beaucoup plus facile de se moquer des collaborateurs. –

+0

C'est une touche plus élaborée que j'avais prévu, mais j'aime ça. Il offre la flexibilité de renvoyer toutes les propriétés dans une liste, tout en préservant les informations de type à travers l'interface. Et cela aide définitivement la testabilité. –

0

Je voudrais avoir le "contrôleur de temp", par une méthode getState, retourner une carte (par exemple en Python un dict, en Javascript un objet, en C++ un std :: map ou std :: hashmap, etc.) de ses propriétés et valeurs actuelles - qu'est-ce qui est compliqué à ce sujet ?! Pourrait être plus simple, il est totalement extensible, et totalement découplé de l'usage qu'il fait (affichage, sérialisation, & c).

0

Si FormatBExporter prend un AdvancedTempController, vous pouvez créer une classe de pont qui rendra TempController conforme à AdvancedTempController. Vous devrez peut-être ajouter une sorte de fonction getFormat() à AdvancedTempController.

Par exemple:

FormatBExporter exporterB; 
TempController tempController; 
AdvancedTempController bridged = TempToAdvancedTempBridge(tempController); 

exporterB.export(bridged); 

Il y a aussi la possibilité d'utiliser un système de cartographie clé-valeur. FormatAExporter exporte/importe une valeur pour la clé "setpoint". FormatBExporter exporte/importe une valeur pour les clés "setpoint" et "ratecontrol". De cette façon, l'ancien FormatAExporter peut toujours lire le nouveau format de fichier (il ignore simplement "ratecontrol") et FormatBExporter peut lire l'ancien format de fichier (si "ratecontrol" est manquant, il utilise une valeur par défaut).

0

Eh bien, beaucoup de choses dépendent des formats de fichiers dont vous parlez. Si elles sont basées sur des combinaisons clé/valeur (y compris celles imbriquées, comme xml), alors avoir une sorte d'objet de mémoire intermédiaire qui est lâchement typé et qui peut être lancé sur l'éditeur de format de fichier approprié est un bon moyen de le faire il. Si ce n'est pas le cas, vous disposez d'un scénario dans lequel vous disposez de quatre combinaisons d'objets et de formats de fichier, avec une logique personnalisée pour chaque scénario. Dans ce cas, il peut ne pas être possible d'avoir une seule représentation pour chaque format de fichier pouvant traiter avec un contrôleur. En d'autres termes, si vous ne pouvez pas généraliser l'éditeur de format de fichier, vous ne pouvez pas le généraliser. Je n'aime pas vraiment l'idée des contrôleurs ayant des exportateurs - je ne suis pas fan des objets qui connaissent les mécanismes de stockage et autres joyeusetés (ils peuvent connaître le concept de stockage, et avoir une instance spécifique donnée à eux via un certain mécanisme DI). Mais je pense que vous êtes d'accord avec cela, et pour à peu près les mêmes raisons.

0

Dans le modèle OO, les méthodes objet en tant que collectif sont le contrôleur. Il est plus utile de séparer votre programme en M et V et pas tellement en C si vous programmez en utilisant OO.

1

en C# ou d'autres langues qui prennent en charge ce que vous pouvez faire ceci:

class TempController { 
    int SetPoint; 
} 
class AdvancedTempController : TempController { 
    int Rate; 
} 

class FormatAExporter { 
    void Export(TempController tc) { 
     Write(tc.SetPoint); 
    } 
} 

class FormatBExporter { 
    void Export(TempController tc) { 
     if (tc is AdvancedTempController) { 
      Write((tc as AdvancedTempController).Rate); 
     } 
     Write(tc.SetPoint); 
    } 
} 
+1

Certes, c'est le moyen le plus facile à court terme. Mais tou utilisé 'est' et 'comme'. Maintenant, vous devez maintenir FormatBExporter à chaque fois pour gérer tous les types différents. Une fois prise à l'extrême, vous aurez une forêt de «si» des déclarations pour comprendre ce que le type est, et le but de ces types était de faire ce genre de chose eux-mêmes. –

Questions connexes