2010-11-30 7 views
2

Taken de wikipedia:Quand une usine est-elle appropriée?

Dans la programmation informatique orienté objet, une usine est un objet pour créer d'autres objets. C'est une abstraction de d'un constructeur, et peut être utilisé pour mettre en œuvre divers schémas d'allocation .

Quelqu'un pourrait-il expliquer quand une classe d'usine est nécessaire ou bénéfique?

Je travaille actuellement sur un projet où j'ai une classe et utilise le constructeur pour initialiser un objet (duh!) Mais il peut échouer et ne pas s'initialiser du tout. J'ai une propriété Success pour vérifier si elle a été créée correctement. Est-ce un bon exemple pour quand une classe d'usine devrait être mise en application? De cette façon, la méthode Create() peut retourner null et je peux me débarrasser de la propriété Success. Ai-je la bonne idée?

+0

en usine ou en usine? – Bozho

Répondre

7

L'exemple de manuel pour une situation d'usine est lorsque vous avez une interface et plusieurs implémentations, mais vous ne voulez pas exposer les implémentations. Vous implémentez une fabrique (méthode ou classe) qui produit des instances de différentes implémentations en fonction des paramètres que vous lui transmettez; Cependant, comme il les renvoie par le type d'interface, l'appelant ne sera pas surchargé de détails d'implémentation.

Exemple dans le monde réel: Supposons que vous ayez défini une interface pour un lecteur de flux et des implémentations à lire à partir d'un fichier local, d'une ressource réseau et de stdin. Vous écrivez ensuite une méthode d'usine qui prend un seul paramètre (l'URI) et renvoie un lecteur approprié. L'appelant n'a pas besoin de connaître les détails de l'implémentation. La meilleure partie est, quand vous décidez que vous voulez soutenir une autre méthode de saisie, disons, data: URIs, vous ajoutez simplement une autre implémentation et l'ajoutez à l'usine - vous n'aurez pas besoin de changer quoi que ce soit dans le code appelant.

+0

... et c'est ce que OCP est tout au sujet de :) Bonne réponse. – Marcus

+0

merci, cela n'a de sens – Marlon

3

L'objectif d'une usine est de déterminer, au moment de l'exécution et donc potentiellement et souvent avec des données indisponibles lors de la compilation, lesquels de class10, tous dérivés d'une classe spécifique avec virtual méthodes, devraient capturer ces données pour votre programme. Le fait est que les fonctionnalités de langage polymorphes vous permettent de travailler avec différents types de données (partageant une classe de base commune), en invoquant les comportements appropriés au type. Pour en bénéficier, vous devez avoir des objets de différents types dérivés. Lorsque vous apprenez à ce sujet dans un cours Comp Sci, vous probablement coder en dur la création de quelques-uns des types dérivés et jouer avec eux à travers des pointeurs vers la classe de base. Dans les problèmes complexes du monde réel, plutôt que dans la création codée en dur, il est souvent provoqué par les données provenant des entrées du programme, telles que les tables de base de données, les fichiers et les sockets. En fonction de ce que vous voyez exactement à chaque point, vous voulez créer un objet correctement typé pour le représenter, mais vous devrez probablement conserver un enregistrement de celui-ci en utilisant un type connu à la compilation: le pointeur vers la classe de base. Ensuite, non seulement vous pouvez préformer les opérations promues par la classe de base - dont certaines peuvent impliquer l'envoi dynamique à l'implémentation de la classe dérivée, mais vous pouvez aussi - si nécessaire - déterminer exactement le type réel des données et appeler les actions en conséquence .

Par exemple, supposons que vous lisez le fichier suivant, qui montre comment vous avez recueilli différents types de données pour chaque:

elephant name Tip-Toes partner Mega 
mule name Dare-You mane_length 132 

Vous avez la hiérarchie de classe suivante pour représenter ceux-ci:

struct Animal 
{ 
    Animal(const std::string& name) : name_(name) { } 

    virtual void eat_from(Supplies&) = 0; // animals must do in their own way... 
    virtual bool can_jump() { return false; } // some animals might, assume not... 

    std::string name_; 
}; 

struct Elephant : Animal 
{ 
    Elephant(const std::string& name, const std::string& partner) 
     : Animal(name), partner_(partner) 
    { } 

    std::string partner_; 

    virtual void eat_from(Supplies&) { supplies.consume(Tofu, 10 * kg); } 
    void swing_trunk(); // something specific to elephants 
}; 

struct Mule : Animal 
{ 
    Mule(const std::string& name, double kgs) : Animal(name), kilograms_(kgs) { } 
    double kilograms_; 

    virtual void eat_from(Supplies&) { supplies.consume(Grass, 2 * kg); } 
    virtual bool can_jump() { return true; } 
}; 

le travail de méthode d'usine est de distinguer l'éléphant de mule et retourner un nouvel objet du type approprié (dérivé - mais pas seulement - des animaux):

Animal* factory(std::istringstream& input) 
{ 
    std::string what, name; 
    if (input >> what && input >> name) 
    { 
     if (what == "elephant") 
     { 
      std::string partner; 
      if (input >> partner) 
       return new Elephant(name, partner); 
     } 
     else if (what == "mule") 
     { 
      double mane_length; 
      if (input >> mane_length) 
       return new Mule(name, mane_length); 
     } 
    } 
    // can only reach here on unparsable input... 
    throw runtime_error("can't parse input"); 
} 

Vous pouvez stocker des animaux * s et effectuer des opérations sur les:

std::vector<Animal*> animals; 
// we expect 30 animals... 
for (int i = 0; i < 30; ++i) animals.push_back(factory(std::cin)); 

// do things to each animal... 
for (int i = 0; i < 30; ++i) 
{ 
    Animal* p_unknown = animals[i]; 

    std::cout << p_unknown->name() << '\n'; 

    if (Elephant* p = dynamic_cast<Elephant*>(p_unknown)) 
     p->swing_trunk(); 
} 

Pour revenir à votre question:

Je travaille actuellement sur un projet où j'ai une classe et d'utiliser la constructeur pour initialiser un objet (duh!) mais il peut échouer et ne pas s'initialiser du tout. J'ai une propriété Success pour vérifier si elle a été créée correctement. Est-ce un bon exemple pour quand une classe d'usine devrait être mise en application? De cette façon, la méthode Create() peut retourner null et je peux me débarrasser de la propriété Success. Ai-je la bonne idée?

Non, pas une situation où une usine est utile, car il y a toujours un seul type impliqué. Il suffit de s'en tenir à ce que vous avez (dans un sens OO), mais vous pouvez choisir de lancer une exception, annuler le programme, etc. plutôt que de définir un drapeau que l'appelé peut ou ne peut pas vérifier.

+0

bonne réponse, y a-t-il une différence entre la méthode des facteurs et la classe d'usine? –

+1

"méthode" est juste un autre nom pour "fonction", et clairement les fonctions peuvent être mises dans une classe pour le groupe toutes les données et les fonctions de soutien dont ils ont besoin - alors vous avez une classe d'usine, mais cela ne change pas vraiment travaillent - juste l'encapsule mieux. Il devient plus intéressant - et significativement différent - lorsque vous avez une classe de fabrique abstraite, ce qui signifie que la fonction d'usine (et/ou le code de support) peut être «virtuelle» et que plusieurs implémentations d'usine peuvent être appelées. Par exemple, sélectionnez une usine "XML-parsing" vs "JSON-parsing" une fois puis utilisez après .... –

+0

Merci Tony. Le commentaire est coloré en orange? –

1

Mon avis sur le modèle d'usine a toujours été quelque peu différent, mais je vais le donner quand même.

Une usine sert à créer un ensemble de types associés. Cela ne signifie pas nécessairement qu'ils implémentent exactement la même interface (s) exactement. Cela signifie que si vous avez un type à instancier, et que vous devez ensuite créer un autre objet et que le type concret (implémentation) du second objet dépend du type concret du premier objet, vous avez besoin d'une fabrique.

Une usine qui crée uniquement un type d'objet (il n'y a pas de "relation") n'est pas une usine mais plutôt une stratégie.

Mes définitions ci-dessus impliquent que Factory n'est pas utilisé ou nécessaire autant que vous ne le pensez.

Et pour les objets qui ne parviennent pas à s'initialiser, je recommande l'approche fail-fast en lançant une exception au lieu de s'appuyer sur un champ d'état.

+0

Leur principale idée du modèle d'usine est que vous cachez différentes implémentations de fonctionnalités similaires en fournissant un mécanisme de création unifié. Que vous utilisiez des interfaces, des classes de base abstraites, des pointeurs de fonction ou des caractères de type canard pour exprimer la relation, est relativement sans importance et dépend du langage de programmation et des préférences personnelles. – tdammers

+0

"Lié signifie que si vous avez un type que vous devez instancier, puis que vous devez créer un autre objet et que le type concret (implémentation) du deuxième objet dépend du type concret du premier objet, vous avez besoin d'une Usine." - Ce n'est pas juste du tout. Une fonction clone satisfait votre définition d'une fabrique, bien qu'elle ne contienne qu'un seul type d'objet. Plus important, vous décrivez une usine abstraite - qui est un sous-ensemble d'usines et considérablement plus limitée et compliquée que le cas simple. –

1

je vais essayer une réponse simple :)

De Wikipedia.

Utilisez le modèle d'usine lorsque:

  • La création de l'objet empêche la réutilisation sans le code de manière significative la duplication.
  • La création de l'objet nécessite l'accès à des informations ou ressources non appropriées à contenir dans l'objet composant.
  • La gestion de la durée de vie des objets créés doit être centralisée pour garantir un comportement cohérent.

Je pense donc que vous n'avez pas besoin d'une usine dans votre cas particulier. Mais je dirais qu'on n'en a pas mal.

Une utilisation courante pour les usines serait si vous voulez retourner une interface et masquer la classe d'implémentation.

Par exemple:

public final class LoginFactory { 

private final static Map<SomeEnum, LoginInterface> IMPLEMENTERS = new HashMap<SomeEnum, LoginInterface>(); 

static { 
    IMPLEMENTERS.put(SomeEnum.QUICK, new QuickerLoginImpl()); 
    IMPLEMENTERS.put(SomeEnum.SECURE, new SecureLoginImpl()); 
} 

public static LoginInterface getLoginImpl(SomeEnum type) { // my naming is bad ... 
    return IMPLEMENTERS.get(type); 
} 

}

De cette façon, vous pouvez changer votre SecureLoginImpl-MoreSecureLoginImpl par exemple sans que l'utilisateur de votre API aperceviez. Vous pouvez également jeter un œil à cette page wiki Abstract Factory pattern.

+0

l'exemple est dans java btw désolé ... Je pourrais vous donner un exemple dans quelque chose d'autre si vous en avez besoin. – Simeon

+0

désolé :) je devrais avoir déclaré la langue dans la question – Marlon

+0

vous en avez besoin en C++? – Simeon