Devrais-je éviter la sous-cuisson par quelque moyen que ce soit lorsque vous utilisez le modèle d'usine?

StackOverflow https://stackoverflow.com/questions/6088411

Question

Je travaille sur un projet de serveur qui implémente un protocole propriétaire. Le serveur est mis en œuvre avec le modèle d'usine en C ++ et nous sommes maintenant confrontés au problème de la sous-emploi.

Le protocole que je travaille est conçu pour le contrôle automatique sur des réseaux lents, tels que RS485, Zigbee, PLC à bande étroite, etc. Nous avons conçu le serveur principal avec un motif usine. Lorsqu'un nouveau cadre est reçu, nous identifions d'abord le type de périphérique associé de cette trame, appelant la méthode d'usine pour générer une nouvelle instance "analyseur" et expédier le cadre à l'instance d'analyse.

Notre protocole exclusif est mis en œuvre dans Pure Binary, toutes les informations dont nous pouvons avoir besoin sont enregistrées dans le cadre lui-même. L'interface de base peut donc être définie aussi simple que possible. Nous mettrions également la mise en œuvre d'une approche auto-enregistrement de notre usine (Code détaillé relativement à STD :: L'opération de carte est omise ici):

// This is our "interface" base-class
class parser
{
public:
    virtual int parse(unsigned char *) = 0;
    virtual ~parser() { }
};

// The next two classes are used for factory pattern
class instance_generator
{
public:
    virtual parser *generate() = 0;
};

class parser_factory
{
private:
    static std::map<int,instance_generator*> classDB;
public:
    static void add(int id, instance_generator &genrator);
    parser *get_instance(int id);
};

// the two template classes are implementations of "auto-regisrtation"
template <class G, int ID> class real_generator : public instance_generator
{
public:
    real_generator()    {   parser_factory::add(ID,this);   }
    parser *generate()  {   return new G;   }
};

template <class T, int N> class auto_reg : virtual public parser
{
private:
    static real_generator<T,N> instance;
public:
    auto_reg() { instance; }
};
template <class T, int N> parser_generator<T,N> auto_reg<T,N>::instance;


// And these are real parser implementations for each device type
class light_sensor : public auto_reg<light_sensor,1>
{
public:
    int parse(unsigned char *str)
    {
        /* do something here */
    }
};

class power_breaker : public auto_reg<power_breaker,2>
{
public:
    int parse(unsigned char *str)
    {
        /* do something here */
    }
};

/* other device parser */

Ce modèle d'usine a très bien fonctionné et il est facile de dépenser de nouveaux types de périphériques.

Cependant, récemment, nous essayons d'interfacer avec un système de contrôle existant offrant une fonctionnalité similiaire. Le système cible est assez ancien et fournit uniquement une interface série ASCII à la manière basée sur commande. Nous avons réussi à résoudre le problème de la communication avec PTY, mais maintenant, le problème à résoudre est la mise en œuvre des analyseurs.

L'interface de commande du système cible est assez limitée. Je ne peux pas simplement attendre et écouter ce qui est entrant, je dois sonder pour l'État, et je dois sonder deux fois - Premier sondage pour en-tête et deuxième sondage pour la charge utile - pour obtenir une commande complète. C'est un problème pour notre mise en œuvre, car j'ai de passer deux images dans l'instance d'analyse afin que cela puisse fonctionner:

class legacy_parser : virtual public parser
{
public:
    legacy_parser() { }
    int parse(unsigned char *str)
    {
        /* CAN NOT DO ANYTHING WITHOUT COMPLETE FRAMES */
    }
    virtual int parse(unsigned char *header, unsigned char *payload) = 0;
};

class legacy_IR_sensor : 
    public legacy_parser,
    public auto_reg<legacy_IR_sensor,20>
{
public:
    legacy_IR_sensor(){ }
    int parse(unsigned char *header, unsigned char *payload)
    {
        /* Now we can finally parse the complete frame */
    }
};

En d'autres termes, nous devrons appeler une méthode de la classe dérivée et la méthode n'est pas définie dans la classe de base. Et nous utilisons le modèle d'usine pour générer l'instance de classe dérivée.

Nous avons maintenant plusieurs choix:

  1. Il suffit de concaténer deux ficelles en une seule ne fonctionne pas. Les deux chaînes contiennent des informations spécifiées par un appareil et elles doivent être analysées séparément. Si nous prenons cette approche, nous effectuerons des "pré-analyses" de l'instance d'analyse avant de pouvoir concaténer la chaîne. Et nous ne pensons pas que ce soit une bonne idée.

  2. Down Downcast Le retour de parser_factory :: get_instance () à Legacy_Parser.

  3. Créez une autre usine indépendante, qui ne contient que des classes dérivées de Legacy_Parser.

  4. Modifiez la définition de l'instance_generator et parser_factory afin qu'elles puissent également générer (Legacy_Parser *), tout en conservant tout le code existant non affecté:

    class instance_generator
    {
    public:
        virtual parser *generate() = 0;
        virtual legacy_parser *generate_legacy() { return NULL; }
    };
    
    class extended_parser_factory : public parser_factory
    {
    public:
        legacy_parser *get_legacy_instance(int id);
    };
    
  5. Implemet "Pointeur intelligent" avec motif de visiteur pour gérer les instances dérivées de Legacy_Parser:

    class smart_ptr
    {
    public:
        virtual void set(parser *p) = 0;
        virtual void set(legacy_parser *p) = 0;
    };
    
    class parser
    {
    public:
        parser() { }
        virtual int parse(unsigned char *) = 0;
        virtual void copy_ptr(smart_ptr &sp)    // implement "Visitor" pattern
        {
            sp.set(this);
        }
        virtual ~parser() { }
    };
    
    class legacy_parser : virtual public parser
    {
    public:
        legacy_parser() { }
        void copy_ptr(smart_ptr &sp)    // implement "Visitor" pattern
        {
            sp.set(this);
        }
        int parse(unsigned char *str)
        {
            /* CAN NOT DO ANYTHING WITHOUT COMPLETE FRAMES */
        }
        virtual int parse(unsigned char *header, unsigned char *payload) = 0;
    };
    
    class legacy_ptr : public smart_ptr
    {
    private:
        parser *miss;
        legacy_parser *hit;
    public:
        legacy_ptr& operator=(parser *rhv)
        {
            rhv->copy_ptr(*this);
            return *this;
        }
        void set(parser* ptr)
        {
            miss=ptr;
            /* ERROR! Do some log or throw exception */
        }
        void set(legacy_parser *ptr)
        {
            hit = ptr;
        }
        legacy_parser& operator*()
        {
            return *hit;
        }
        ~legacy_ptr()
        {
            if(miss) {
                delete miss;
            }
            if(hit) {
                delete hit;
            }
        }
    };
    
  6. Il est évident que la rédaction de Dynamic_cast <> est l'approche la plus facile pour nous, mais aucun de nous n'aime cette idée, car nous estimons tous que c'est "mal" de descendre quelque chose. Cependant, personne ne peut expliquer exactement pourquoi c'est "mal".

    Avant de prendre une décision, je souhaiterais entendre plus de commentaires sur ces options.

Était-ce utile?

La solution

http://en.wikipedia.org/wiki/circle-ellipse_problème estvotre 1er exemple de mal. Si vous voyez que vous pouvez faire quelque chose avec violation des principes de base, vous devriez inventer une autre roue ou essayer un autre chapeau: http://en.wikipedia.org/wiki/six_thinking_hat

Autres conseils

Downcasting, surtout dans une mise en œuvre des motifs d'usine, il est un bon sens pour moi.Il coule bien avec le "programme à l'interface" idéologie.Je ne sais pas pourquoi les gens sentent que le refuge de la cuisson est mauvais.Découvrez Contravariance Comme c'est ce que vous regardez.

Le problème est que Legacy_Parser s'attend à ce que deux images plutôt qu'une dans votre analyseur d'origine.Donc, une solution possible consiste à modifier légèrement votre analyse d'un analyseur d'origine et à le faire fonctionner avec plus d'un cadres. Par exemple, l'analyse peut renvoyer une constante prédéfinie si l'analyseur veut plus de cadres, puis legacy_Parser peut être mis en œuvre comme ceci:

class legacy_parser : public parser {
 public:
  int parse(unsigned char *str) {
    if (parse_header_) {
      // store str in header_
      parse_header_ = false;
      return kExpectMoreFrames;
    } else {
      return parse(header_, str);
    }
  }
 private:
  int parse(unsigned char *header, unsigned char *parload) {
    // ...
  }
  bool parse_header_;
  unsigned char *header_;
};

Le code d'analyseur existant ne doit pas être affecté si elles n'utilisent pas accidentellement la valeur définie pour la signification de «plus de cadres».

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top