web-dev-qa-db-fra.com

Modèle d'usine. Quand utiliser les méthodes d'usine?

Quand est-il judicieux d'utiliser des méthodes factory dans un objet au lieu d'une classe Factory?

249
jjshell

J'aime penser aux modèles de conception en termes de «personnes» dans mes classes, et les modèles sont la manière dont les gens se parlent.

Donc, pour moi, le modèle d'usine est comme une agence de recrutement. Vous avez quelqu'un qui aura besoin d'un nombre variable de travailleurs. Cette personne peut savoir certaines informations dont elle a besoin parmi les personnes qu’elle embauche, mais c’est tout.

Alors, quand ils ont besoin d’un nouvel employé, ils appellent l’agence d’embauche et leur disent ce dont ils ont besoin. Maintenant, pour engager quelqu'un, vous devez connaître beaucoup de choses - avantages, vérification de l'éligibilité, etc.

De la même manière, utiliser une fabrique permet au consommateur de créer de nouveaux objets sans avoir à connaître en détail la façon dont ils ont été créés ni leurs dépendances. Il leur suffit de donner les informations qu'ils souhaitent réellement.

public interface IThingFactory
{
    Thing GetThing(string theString);
}

public class ThingFactory : IThingFactory
{
    public Thing GetThing(string theString)
    {
        return new Thing(theString, firstDependency, secondDependency);
    }
}

Ainsi, le consommateur de ThingFactory peut désormais obtenir une chose sans avoir à connaître les dépendances de celle-ci, à l'exception des données de chaîne qui proviennent du consommateur.

355
kyoryu

Les méthodes d'usine devraient être considérées comme une alternative aux constructeurs - principalement lorsque les constructeurs ne sont pas assez expressifs, c'est-à-dire.

class Foo{
  public Foo(bool withBar);
}

n'est pas aussi expressif que:

class Foo{
  public static Foo withBar();
  public static Foo withoutBar();
}

Les classes d'usine sont utiles lorsque vous avez besoin d'un processus compliqué pour construire l'objet, lorsque la construction nécessite une dépendance que vous ne voulez pas pour la classe réelle, lorsque vous devez construire différents objets, etc.

84
Rasmus Faber

Une situation dans laquelle je trouve personnellement que des classes Factory distinctes ont un sens est lorsque l'objet final que vous essayez de créer repose sur plusieurs autres objets. E.g, en PHP: Supposons que vous ayez un objet House, qui à son tour comporte un objet Kitchen et un objet LivingRoom, et que l’objet LivingRoom comporte également un objet TV

La méthode la plus simple pour y parvenir consiste à demander à chaque objet de créer ses enfants à l'aide de sa méthode de construction. Toutefois, si les propriétés sont relativement imbriquées, lorsque votre House échoue, vous passerez probablement un certain temps à essayer d'isoler exactement ce qui échoue.

L'alternative consiste à effectuer les opérations suivantes (injection de dépendance, si vous aimez le terme de fantaisie):

$TVObj = new TV($param1, $param2, $param3);
$LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
$KitchenroomObj = new Kitchen($param1, $param2);
$HouseObj = new House($LivingroomObj, $KitchenroomObj);

Ici, si le processus de création d'une House échoue, il n'y a qu'un seul endroit où regarder, mais le fait d'utiliser ce bloc à chaque fois que l'on veut une nouvelle House est loin d'être commode. Entrez les usines:

class HouseFactory {
    public function create() {
        $TVObj = new TV($param1, $param2, $param3);
        $LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
        $KitchenroomObj = new Kitchen($param1, $param2);
        $HouseObj = new House($LivingroomObj, $KitchenroomObj);

        return $HouseObj;
    }
}

$houseFactory = new HouseFactory();
$HouseObj = $houseFactory->create();

Grâce à la fabrique ici, le processus de création d'une House est résumé (en ce sens que vous n'avez pas besoin de créer et de configurer chaque dépendance lorsque vous voulez juste créer une House) et en même temps centralisé, ce qui facilite la maintenance. . L'utilisation de différentes fabriques peut être bénéfique pour d'autres raisons (par exemple, la testabilité), mais je trouve ce cas d'utilisation spécifique pour illustrer au mieux la façon dont les classes Factory peuvent être utiles.

66
Mahn

Il est important de bien différencier l’idée qui sous-tend l’usine ou la méthode d’usine. Les deux sont destinés à résoudre différents types de problèmes de création d’objets qui s’excluent mutuellement.

Soyons précis sur la "méthode d'usine":

La première chose à faire est que, lorsque vous développez une bibliothèque ou des API qui seront à leur tour utilisées pour le développement d'applications ultérieures, la méthode d'usine est l'une des meilleures sélections pour le modèle de création. Raison derrière; Nous savons que pour créer un objet avec la ou les fonctionnalités requises, mais que le type d’objet restera indécis ou que les paramètres dynamiques seront transmis .

Le problème est qu’en fait, on peut obtenir à peu près la même chose en utilisant le modèle d’usine lui-même, mais un inconvénient majeur introduira dans le système si le modèle d’usine est utilisé pour le problème souligné ci-dessus, c’est que votre logique de création de soyez spécifique à certaines conditions de l'entreprise, si à l'avenir vous devez étendre les fonctionnalités de votre bibliothèque à d'autres plates-formes (plus techniquement, vous devez ajouter plus de sous-classes d'interface de base ou de classe abstraite afin que la fabrique renvoie ces objets en plus de ceux existants. sur la base de certains paramètres dynamiques) puis chaque fois que vous devez changer (étendre) la logique de la classe d'usine qui sera un fonctionnement coûteux et pas bon du point de vue de la conception . D'un autre côté, si le motif "méthode d'usine" sera utilisé pour effectuer la même chose, il vous suffit de créer des fonctionnalités supplémentaires (sous-classes) et de les enregistrer de manière dynamique par injection, ce qui ne nécessite aucune modification de votre code de base.

interface Deliverable 
{
    /*********/
}

abstract class DefaultProducer 
{

    public void taskToBeDone() 
    {   
        Deliverable deliverable = factoryMethodPattern();
    }
    protected abstract Deliverable factoryMethodPattern();
}

class SpecificDeliverable implements Deliverable 
{
 /***SPECIFIC TASK CAN BE WRITTEN HERE***/
}

class SpecificProducer extends DefaultProducer 
{
    protected Deliverable factoryMethodPattern() 
    {
        return new SpecificDeliverable();
    }
}

public class MasterApplicationProgram 
{
    public static void main(String arg[]) 
    {
        DefaultProducer defaultProducer = new SpecificProducer();
        defaultProducer.taskToBeDone();
    }
}
16
Prakash Chhipa

C'est une bonne idée d'utiliser méthodes d'usine objet intérieur lorsque:

  1. La classe de l'objet ne sait pas exactement quelles sous-classes il doit créer
  2. La classe de l'objet est conçue pour que les objets créés soient spécifiés par des sous-classes
  3. La classe d'objets délègue ses tâches à des sous-classes auxiliaires et ne sait pas quelle classe exacte prendra ces tâches.

C'est une bonne idée d'utiliser abstract factory class lorsque:

  1. Votre objet ne doit pas dépendre de la manière dont ses objets internes sont créés et conçus
  2. Le groupe d'objets liés doit être utilisé ensemble et vous devez servir cette contrainte
  3. L'objet doit être configuré par l'une des familles possibles d'objets liés qui feront partie de votre objet parent.
  4. Il est nécessaire de partager des objets enfants affichant uniquement les interfaces, mais pas une implémentation.
14
Dzianis Yafimau

Ils sont également utiles lorsque vous avez besoin de plusieurs "constructeurs" avec le même type de paramètre mais avec un comportement différent. 

14
Rik

UML de 

 enter image description here

Product: Définit une interface des objets créés par la méthode Factory.

ConcreteProduct: Implémente l'interface du produit.

Creator: Déclare la méthode Factory.

ConcreateCreator: Implémente la méthode Factory pour renvoyer une instance d'un ConcreteProduct.

Déclaration du problème: Créer une fabrique de jeux en utilisant les méthodes de fabrique, qui définissent l'interface du jeu.

Extrait de code:

import Java.util.HashMap;


/* Product interface as per UML diagram */
interface Game{
    /* createGame is a complex method, which executes a sequence of game steps */
    public void createGame();
}

/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game{
    public Chess(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Chess game");
        System.out.println("Opponents:2");
        System.out.println("Define 64 blocks");
        System.out.println("Place 16 pieces for White opponent");
        System.out.println("Place 16 pieces for Black opponent");
        System.out.println("Start Chess game");
        System.out.println("---------------------------------------");
    }
}
class Checkers implements Game{
    public Checkers(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Checkers game");
        System.out.println("Opponents:2 or 3 or 4 or 6");
        System.out.println("For each opponent, place 10 coins");
        System.out.println("Start Checkers game");
        System.out.println("---------------------------------------");
    }
}
class Ludo implements Game{
    public Ludo(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Ludo game");
        System.out.println("Opponents:2 or 3 or 4");
        System.out.println("For each opponent, place 4 coins");
        System.out.println("Create two dices with numbers from 1-6");
        System.out.println("Start Ludo game");
        System.out.println("---------------------------------------");
    }
}

/* Creator interface as per UML diagram */
interface IGameFactory {
    public Game getGame(String gameName);
}

/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory {

     HashMap<String,Game> games = new HashMap<String,Game>();
    /*  
        Since Game Creation is complex process, we don't want to create game using new operator every time.
        Instead we create Game only once and store it in Factory. When client request a specific game, 
        Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
    */

    public GameFactory(){

        games.put(Chess.class.getName(),new Chess());
        games.put(Checkers.class.getName(),new Checkers());
        games.put(Ludo.class.getName(),new Ludo());        
    }
    public Game getGame(String gameName){
        return games.get(gameName);
    }
}

public class NonStaticFactoryDemo{
    public static void main(String args[]){
        if ( args.length < 1){
            System.out.println("Usage: Java FactoryDemo gameName");
            return;
        }

        GameFactory factory = new GameFactory();
        Game game = factory.getGame(args[0]);
        if ( game != null ){                    
            game.createGame();
            System.out.println("Game="+game.getClass().getName());
        }else{
            System.out.println(args[0]+  " Game does not exists in factory");
        }           
    }
}

sortie:

Java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
Game=Chess

Cet exemple montre une classe Factory en implémentant une FactoryMethod.

  1. Game est l'interface pour tout type de jeux. Il définit une méthode complexe: createGame()

  2. Chess, Ludo, Checkers sont différentes variantes de jeux, qui permettent une implémentation de createGame()

  3. public Game getGame(String gameName) est FactoryMethod dans IGameFactory class

  4. GameFactory pré-crée différents types de jeux dans le constructeur. Il implémente la méthode d'usine IGameFactory

  5. le nom du jeu est passé comme argument de ligne de commande à NotStaticFactoryDemo

  6. getGame dans GameFactory accepte un nom de jeu et retourne l'objet Game correspondant.

Usine:

Crée des objets sans exposer la logique d'instanciation au client.

FactoryMethod

Définissez une interface pour créer un objet, mais laissez les sous-classes décider de la classe à instancier. La méthode Factory permet à une classe de différer l'instanciation en sous-classes

Cas d'utilisation:

Quand utiliser: Client ne sait pas quelles classes concrètes il sera nécessaire de créer au moment de l'exécution, mais veut juste obtenir une classe qui fera le travail.

9
Ravindra babu

C'est vraiment une question de goût. Les classes d'usine peuvent être abstraites/interfacées si nécessaire, alors que les méthodes d'usine sont plus légères (et ont tendance à être testables, car elles n'ont pas de type défini, mais elles nécessiteront un point d'enregistrement bien connu, semblable à un service locator mais pour localiser les méthodes d’usine).

5
Brad Wilson

Les classes d'usine sont utiles lorsque le type d'objet renvoyé a un constructeur privé, lorsque différentes classes d'usine définissent différentes propriétés sur l'objet renvoyé ou lorsqu'un type de fabrique spécifique est associé à son type concret renvoyé. 

WCFutilise les classes ServiceHostFactory pour récupérer des objets ServiceHost dans différentes situations. IIS utilise ServiceHostFactory standard pour extraire les instances ServiceHost pour les fichiers .svc, mais WebScriptServiceHostFactory est utilisé pour les services qui renvoient des sérialisations aux clients JavaScript. ADO.NET Data Services a son propre DataServiceHostFactory spécial et ASP.NET a son ApplicationServicesHostFactory puisque ses services ont des constructeurs privés.

Si une seule classe consomme l'usine, vous pouvez simplement utiliser une méthode de fabrique au sein de cette classe.

4
Mark Cidade

Envisagez un scénario lorsque vous devez concevoir une classe Order et Customer. Pour des raisons de simplicité et d’exigences initiales, vous ne vous sentez pas besoin d’usine pour la classe Ordre et remplissez votre application avec de nombreuses instructions 'new Order ()'. Les choses fonctionnent bien.

Désormais, une nouvelle exigence s'impose: l'objet Order ne peut être instancié sans l'association du client (nouvelle dépendance). Maintenant, vous avez des considérations suivantes.

1- Vous créez une surcharge de constructeur qui ne fonctionnera que pour les nouvelles implémentations. (Non acceptable) . 2- Vous changez les signatures Order () et changez chaque invocation. (Pas une bonne pratique et une vraie douleur).

Si vous avez créé une usine pour Order Class, il vous suffit de modifier une ligne de code et vous êtes prêt à partir. Je suggère la classe Factory pour presque toutes les associations d'agrégats. J'espère que cela pourra aider.

3
Muhammad Awais

Selon le site Web de sourcemaking, ses intentions sont les suivantes:

  • Définissez une interface pour créer un objet, mais laissez les sous-classes décider de la classe à instancier. La méthode d'usine permet à une classe de différer l'instanciation en sous-classes.

  • Définir un constructeur "virtuel".

  • Le nouvel opérateur considéré comme nuisible.

Un exemple de la façon dont il peut être utilisé:

abstract class AbstractFactoryMethod {
    abstract function makePHPBook($param);
}

class OReillyFactoryMethod extends AbstractFactoryMethod
{
    function makePHPBook($param)
    {
        $book = NULL;  
        switch ($param) {
            case "us":
                $book = new OReillyPHPBook();
            break;
            // Other classes...
            case "other":
                $book = new SamsPHPBook();
            break;
            default:
                $book = new OReillyPHPBook();
            break;        
    }

    return $book;
}

Et puis les tests:

function testFactoryMethod($factoryMethodInstance)
{
    $phpUs = $factoryMethodInstance->makePHPBook("us");
    echo 'us php Author: '.$phpUs->getAuthor();
    echo 'us php Title: '.$phpUs->getTitle();
}

echo 'Testing OReillyFactoryMethod';
$factoryMethodInstance = new OReillyFactoryMethod();
testFactoryMethod($factoryMethodInstance);
3
Alexander Beat

Toute classe qui reporte la création d'objet vers sa sous-classe pour l'objet avec lequel elle doit travailler peut être considérée comme un exemple de modèle Factory.

J'ai mentionné en détail dans une autre réponse à https://stackoverflow.com/a/49110001/504133

1
nits.kk

Je compare les usines au concept de bibliothèques. Par exemple, vous pouvez avoir une bibliothèque pour travailler avec des nombres et une autre pour travailler avec des formes. Vous pouvez stocker les fonctions de ces bibliothèques dans des répertoires nommés logiquement sous la forme Numbers ou Shapes. Ce sont des types génériques pouvant inclure des entiers, des flottants, des dobules, des longs ou des rectangles, des cercles, des triangles, des pentagones dans le cas des formes.

Le fabricant utilise le polymorphisme, l’injection de dépendance et l’inversion de contrôle.

Le but déclaré des modèles d'usine est: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Supposons donc que vous construisez un système d'exploitation ou une structure, ainsi que tous les composants discrets.

Voici un exemple simple du concept du modèle d'usine en PHP. Je ne suis peut-être pas à 100% sur tout cela, mais c'est destiné à servir d'exemple simple. Je ne suis pas un expert.

class NumbersFactory {
    public static function makeNumber( $type, $number ) {
        $numObject = null;
        $number = null;

        switch( $type ) {
            case 'float':
                $numObject = new Float( $number );
                break;
            case 'integer':
                $numObject = new Integer( $number );
                break;
            case 'short':
                $numObject = new Short( $number );
                break;
            case 'double':
                $numObject = new Double( $number );
                break;
            case 'long':
                $numObject = new Long( $number );
                break;
            default:
                $numObject = new Integer( $number );
                break;
        }

        return $numObject;
    }
}

/* Numbers interface */
abstract class Number {
    protected $number;

    public function __construct( $number ) {
        $this->number = $number;
    }

    abstract public function add();
    abstract public function subtract();
    abstract public function multiply();
    abstract public function divide();
}
/* Float Implementation */
class Float extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Integer Implementation */
class Integer extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Short Implementation */
class Short extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Double Implementation */
class Double extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Long Implementation */
class Long extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}

$number = NumbersFactory::makeNumber( 'float', 12.5 );
0
Robert Rocha

Les classes d'usine sont plus lourdes, mais vous offrent certains avantages. Dans les cas où vous devez créer vos objets à partir de plusieurs sources de données brutes, ils vous permettent d'encapsuler uniquement la logique de construction (et peut-être l'agrégation des données) à un seul endroit. Là, il peut être testé en résumé sans se préoccuper de l'interface objet.

J'ai trouvé ce modèle utile, en particulier lorsque je suis incapable de remplacer un ORM inadéquat et que je souhaite instancier efficacement de nombreux objets à partir de jointures de table de base de données ou de procédures stockées.

0
jonfm

Exemple AbstractFactory.

    TypeImpl<String> type = new TypeImpl<>();
    type.addType("Condition");
    type.addType("Hazardous");

    AbstractTypeFactory<String, Tag> tags = new AbstractTypeFactory<String, Tag>(type) {

        @Override
        public Tag create(String string) {
            String tp = type.find(string);

            switch (tp) {
                case "Hazardous":
                    return new HazardousTag();
                case "Condition":
                    return new ConditionTag();
                default:
                    return null;
            }
        }
    };

    Tag tagHazardous = tags.create("Hazardous");
    Tag tagCondition = tags.create("Condition");

}
0
Vahe Gharibyan

si vous voulez créer un objet différent en termes d’utilisation. C'est utile.

public class factoryMethodPattern {
      static String planName = "COMMERCIALPLAN";
      static int units = 3;
      public static void main(String args[]) {
          GetPlanFactory planFactory = new GetPlanFactory();
          Plan p = planFactory.getPlan(planName);
          System.out.print("Bill amount for " + planName + " of  " + units
                        + " units is: ");
          p.getRate();
          p.calculateBill(units);
      }
}

abstract class Plan {
      protected double rate;

      abstract void getRate();

      public void calculateBill(int units) {
            System.out.println(units * rate);
      }
}

class DomesticPlan extends Plan {
      // @override
      public void getRate() {
            rate = 3.50;
      }
}

class CommercialPlan extends Plan {
      // @override
      public void getRate() {
            rate = 7.50;
      }
}

class InstitutionalPlan extends Plan {
      // @override
      public void getRate() {
            rate = 5.50;
      }
}

class GetPlanFactory {

      // use getPlan method to get object of type Plan
      public Plan getPlan(String planType) {
            if (planType == null) {
                  return null;
            }
            if (planType.equalsIgnoreCase("DOMESTICPLAN")) {
                  return new DomesticPlan();
            } else if (planType.equalsIgnoreCase("COMMERCIALPLAN")) {
                  return new CommercialPlan();
            } else if (planType.equalsIgnoreCase("INSTITUTIONALPLAN")) {
                  return new InstitutionalPlan();
            }
            return null;
      }
}
0
Samet öztoprak