2010-03-13 4 views
3

J'ai une classe de fabrique pour construire des objets de classe de base B. L'objet (D) qui utilise cette usine reçoit une liste de chaînes représentant les types réels. Quelle est la mise en œuvre correcte:Question concernant le modèle d'usine

  1. l'usine reçoit un Enum (et utilise l'interrupteur dans la fonction Créer) et D est responsable de convertir la chaîne en Enum.
  2. l'usine reçoit une chaîne et vérifie une correspondance avec un ensemble de chaînes valides (en utilisant ifs)
  3. autre implémentation à laquelle je n'ai pas pensé.

Répondre

0

Vous pouvez placer toutes les chaînes correspondantes dans l'ensemble ou la liste et vérifier si elles contiennent vos chaînes au lieu d'écrire ifs/switches.

+0

La fabrique doit toujours construire l'objet correct en fonction de la chaîne. Il faut faire ifs/switches – amitlicht

+1

Il est possible d'avoir une fonction de carte -> creator qui est bien meilleure que ifs/switches. –

+0

Je pense que la commutation Enum est inévitable. Vous le ferez de toute façon, explicitement ou implicitement. – abatishchev

1

Je voudrais séparer la conversion des chaînes en énumération en un objet distinct. Cela peut facilement être résolu par une carte btw. Mais la manipulation des erreurs, etc., est toujours quelque chose que ni D ni l'usine ne devraient s'inquiéter.

Ensuite, soit D appelle le convertisseur pour obtenir son enum, ou il est déjà converti au préalable, donc D doit seulement passer l'énumération à l'usine. (Btw l'usine ferait mieux d'utiliser une carte à la place d'un commutateur en interne). Cela soulève la question suivante: avez-vous réellement besoin des énumérations (ailleurs que dans D et dans l'usine)? Sinon, peut-être l'énumération pourrait être laissée en dehors de l'image et vous pourriez utiliser une carte pour convertir directement des chaînes en types (ie - puisque C++ ne supporte pas le chargement dynamique des classes - pour fonctionner les objets qui créent les instances toi). Un exemple rugueux (je ne dispose pas d'un IDE pour le tester porte donc avec moi s'il y a des erreurs dans celui-ci):

// Function type returning a pointer to B 
typedef (B*)(*func)() StaticConstructor; 

// Function creating instances of subclass E 
B* createSubclassE() { 
    return new E(...); 
} 

// Function creating instances of subclass F 
B* createSubclassF() { 
    return new F(...); 
} 

// Mapping from strings to constructor methods creating specific subclasses of B 
map<string, StaticConstructor> factoryMap; 
factoryMap["E"] = &createSubclassE; 
factoryMap["F"] = &createSubclassF; 

Bien sûr, les instances créées doivent également être éliminés de façon appropriée - dans le code de production , les objets retournés pourraient être par exemple enfermé dans un auto_ptr. Mais j'espère que ce court exemple est suffisant pour vous montrer l'idée de base. Voici a tutorial si vous voulez plus ...

+0

que voulez-vous dire par chaîne pour taper la carte? à quoi devrait ressembler cette carte? – amitlicht

+0

@eriks J'ai ajouté un exemple. –

0

Mon projet sur VC++/Qt avait un grand nombre de fichiers XML contenant des chaînes qui avaient une représentation Enum dans la source.

Ainsi, pour chaque Enum que nous avions une enveloppe avec l'opérateur surchargée QString <>Enum:

enum DataColumnTypeEnum 
{ 
    DataColumnTypeNotSet, 
    ColumnBinary, 
    ColumnBoolean, 
    ColumnDate, 
    ColumnDateTime, 
    ColumnNumber, 
    ColumnFloat, 
    ColumnPrimary, 
    ColumnString, 
    ColumnText, 
}; 

class DataColumnType 
{ 
public: 
    DataColumnType(); 
    DataColumnType(DataColumnTypeEnum); 
    DataColumnType(const QString&); 

    DataColumnType& operator = (DataColumnTypeEnum); 
    DataColumnType& operator = (const QString&); 

    operator DataColumnTypeEnum() const; 
    operator QString() const; 
private: 
    DataColumnTypeEnum type; 
}; 

DataColumnType& DataColumnType::operator = (const QString& str) 
{ 
    str.toLower(); 
    if(str.isEmpty()) type = DataColumnTypeNotSet; 
    else if(str == "binary") type = ColumnBinary; 
    else if(str == "bool") type = ColumnBoolean; 
    else if(str == "date") type = ColumnDate; 
    else if(str == "datetime") type = ColumnDateTime; 
    else if(str == "number") type = ColumnNumber; 
    else if(str == "float") type = ColumnFloat; 
    else if(str == "primary") type = ColumnPrimary; 
    else if(str == "string") type = ColumnString; 
    else if(str == "text") type = ColumnText; 
    return *this; 
} 

mais l'approche dans la liste des derniers est très laid.

Il est préférable de créer une table de hachage statique ou un dictionnaire et de rechercher dans.

0

La manière normale est d'avoir votre usine comme un singleton. Ensuite, chaque classe basée sur la classe B enregistre sa fonction de création et son nom avec l'usine à l'instant d'initialisation statique. Ceci est souvent fait avec des macros. L'usine peut alors créer une table de hachage rapide de ces noms pour créer des fonctions. Etc ... vous obtenez la dérive.

+1

Je ne suis pas d'accord. Une usine n'est pas nécessairement un Singleton du tout. En fait, comme les Singletons rendent les tests unitaires difficiles, il vaut mieux les éviter à moins que cela ne soit vraiment nécessaire. –

+0

Merci Péter (+1 sur le commentaire). Je suis d'accord que l'usine elle-même n'a pas besoin d'être un singleton mais la liste des classes doit exister d'une manière ou d'une autre et je préfère la construire avec l'initialisation statique. Au lieu de dans une fonction ou même un fichier de données. Lorsque vous travaillez dans de grandes équipes, cela empêche les gens de combattre d'autres fichiers. D'autres méthodes peuvent être plus souhaitables en fonction des besoins. –

0

J'utilise personnellement une énumération améliorée parce que j'ai toujours trouvé l'énumération de C++ manquante: des messages comme Type 3 - method -begin ne sont pas très instructifs.

Pour cette façon, j'utilise une classe simple basé sur un modèle:

template <class Holder> 
class Enum 
{ 
public: 
    typedef typename Holder::type enum_type; 

    Enum(): mValue(Invalid()) {} 
    Enum(enum_type i): mValue(Get(i)) {} 
    explicit Enum(const std::string& s): mValue(Get(s)) {} 

    bool isValid() const { return mValue != Invalid(); } 
    enum_type getValue() const { return mValue->first; } 

private: 
    typedef typename Holder::mapping_type mapping_type; 
    typedef typename mapping_type::const_iterator iterator; 
    static const mapping_type& Mapping() { static mapping_type MMap = Holder::Initialize(); return MMap; } 

    static iterator Invalid() { return Mapping().end(); } 
    static iterator Get(enum_type i) { // search } 
    static iterator Get(const std::string& s) { // search } 

    iterator mValue; 
}; 

Vous définissez Holder comme ceci:

struct Example 
{ 
    typedef enum { 
    Value1, 
    Value2, 
    Value3 
    } type; 

    typedef std::vector< std::pair< type, std::string > > mapping_type; 

    static mapping_type Initialize() { 
    return builder<mapping_type>()(Value1,"Value1")(Value2,"Value2")(Value3,"Value3"); 
    } 
}; 

Vous pouvez définir une macro pour elle:

DEFINE_ENUM(Example, (Value1)(Value2)(Value3)) 

Mais je laisse la mise en œuvre comme un exercice (Boost.Preprocessor est votre ami).

La chose cool est de l'utiliser!

int main(int argc, char* argv[]) 
{ 
    std::string s; 
    std::cin >> s; 
    Enum<Example> e(s); 

    switch(e.getValue()) 
    { 
    case Example::Value1: 
    case Example::Value2: 
    ++e; 
    case Example::Value3: 
    std::cout << e << std::endl; 
    default: 
    } 
}