2009-04-02 7 views
5

J'essaye d'écrire un modèle d'usine pour créer un mode principal ou un mode d'essai dans mon programme. Le code que j'utilisais précédemment pour créer ces objets était:Débutant: modèle d'usine en Java

play = (isMode) ? new MainMode(numberRanges, numberOfGuesses) : 
        new TestMode(numberRanges, numberOfGuesses, randNo()); 

Mon jeu (jeu) ne soit créer un objet mainmode ou un objet TestMode en fonction d'une valeur booléenne (isMode). Comme vous pouvez le voir, j'ajoute une valeur supplémentaire dans mon objet TestMode (randNo()). Cette valeur est utilisée dans TestMode pour permettre à l'utilisateur d'entrer son propre "nombre aléatoire", alors que dans le constructeur MainMode, ceci a été généré aléatoirement. Dans ce programme, MainMode et TestMode sont des sous-classes du jeu de classe abstrait.

Maintenant, je veux remplacer cette ligne avec un motif d'usine, bien que je ne suis pas sûr car mon constructeur TestMode nécessite un objet supplémentaire et je ne sais pas où je devrais passer cette valeur. Si je devais créer une Usine, il faudrait qu'elle appartienne à une nouvelle classe, probablement nommée GameFactory ou ModeFactory ou quelque chose du genre.

Comment procéder?

EDIT: Le problème ici est que le code ci-dessus est dans mon interface graphique, où les valeurs de numberRanges, numberOfGuesses et la méthode randNo() sont. Je veux créer une classe Factory mais je suis incapable de transmettre ces valeurs parce que randNo() s'active. Voici ma méthode randNo().

private int randNo() { 
    boolean isValidNumber = true; 
    int testRandomNum = 0; 
    while(isValidNumber) { 
     try { 
      testRandomNum = Integer.parseInt(JOptionPane.showInputDialog("Enter Random Number")); 
      isValidNumber = false; 
     } catch (NumberFormatException e) { 
      JOptionPane.showMessageDialog(null, "Sorry, but the number you entered was invalid"); 
     } 
    } 

    return testRandomNum; 
} 

Le problème est que chaque fois que je passe randNo(), il affiche le JOptionPane. Comme je l'ai déjà dit, l'interface graphique et la logique sont séparées. L'interface utilisateur graphique se trouve dans un package graphique tandis que le reste du code se trouve dans le package logique.

Répondre

12

Notez que certaines des autres réponses peuvent sans doute décrire des usines, mais ne décrivent pas le GOF Factory Pattern.

Maintenant, je veux remplacer cette ligne avec un motif d'usine , bien que je ne suis pas sûr que mon constructeur TestMode nécessite un objet supplémentaire et je ne suis pas sûr où je aurais besoin de passer cette valeur.

Eh bien, vous pourriez penser de cette façon: MainMode, pas TestMode, est celui qui fait quelque chose de spécial. La chose spéciale qu'il fait, est de ignorer le nombre donné, afin de s'assurer qu'il est vraiment aléatoire. Dans cette façon de penser, c'est MainMode qui fait quelque chose de plus. Ou, si autre que le caractère aléatoire, MainMode et TestMode ne sont pas différents, alors vous pensez peut-être que vous pouvez factoriser cette similarité dans une classe, qui est fourni une des deux stratégies pour calculer des nombres aléatoires. Une stratégie serait en fait aléatoire, et l'autre serait perverse, avec une fourchette aléatoire de seulement 1 valeur. Mais supposons qu'il existe d'autres différences entre MainMode et TestMode - il est probable que TestMode génère un débogage supplémentaire sur System.out ou quelque chose comme ça.

Nous pouvons encore factoriser « comment pouvons-nous fournissons hasard » de sommes que nous testons ou jouer le jeu pour de vrai ». Ce sont orthogonales préoccupations.

Alors maintenant, nous savons que, en plus de tout ce que un 'Mode fait, il devrait accepter une stratégie aléatoire.Alors nous pourrions, par exemple, quand on vous dit que la plate-forme standard aléatoire n'est pas vraiment assez aléatoire, vous pouvez le remplacer par un meilleur aléatoire

Ou vous peut faire des tests où la plage de randoms est limitée à seulement deux choix, ou alterne toujours de un à zéro, ou retourne sur chaque appel la valeur suivante dans certains Vecrtor ou Iterat ou.

nous utilisons donc les stratégies de modèle Stratégie GOF pour construire les de Hasard:

interface RandomStrategy { 
    public double random(); 
} 

public class NotSoRandom implements RandomStrategy { 
    private double r; 
    public NotSoRandom(final double r) { this.r = r; } 
    public double random() { return r; } 
} 

public class PlatformRandom implements RandomStrategy { 
    public double random() { return Math.random(); } 
} 

Maintenant, si votre application entière ne jamais crée un « mode, il n'y a pas besoin d'une usine; vous utilisez une fabrique lorsque vous devez créer le même type de classe encore et encore; L'usine n'est en fait qu'une stratégie pour créer le bon type de (sous-) classe.

Dans le code de production, j'ai utilisé des usines où j'ai une classe générique qui crée des choses, et je dois dire comment créer la bonne sous-classe à créer; Je passe dans une usine pour le faire.

Maintenant, nous créons un motif d'usine pour le 'Mode'; ce sera étonnamment semblable au modèle de stratégie:

abstract class Mode() { 
private RandomStrategy r; 
public Mode(final RandomStrategy r) { this.r = r; } 
// ... all the methods a Mode has 
} 

public class MainMode implements Mode { 
    public MainMode(final RandomStrategy r) { super(r); } 
} 

public class TestMode implements Mode { 
    public TestMode(final RandomStrategy r) { super(r); } 
} 

interface ModeFactory{ 
    public Mode createMode(final RandomStrategy r); 
} 

public class MainFactory() { 
    public Mode createMode(final RandomStrategy r) { 
     return new MainMode(r); 
    } 
} 

public class TestFactory() { 
    public Mode createMode(final RandomStrategy r) { 
     return new TestMode(r); 
    } 
} 

Alors maintenant, vous savez sur le modèle de l'usine et modèle de stratégie, et la façon dont ils sont similaires en « forme », mais différent dans la façon dont ils sont utilisés: l'usine Pattern est Object Creational et renvoie un objet à utiliser; La stratégie est Object Behavioral et une instance est généralement créée explicitement et une référence est conservée dans l'instance pour encapsuler un algorithme. Mais en termes de structure, ils sont assez similaires. Editer: l'OP demande, dans un commentaire, "Comment est-ce que j'intégrerais ceci dans mon interface graphique?"

Eh bien, rien de tout cela n'appartient à l'interface graphique de votre programme, sauf peut-être le «Mode». Vous créez la ConcreteStrategy et la transmettez à l'Usine préférée dans une routine d'installation, en déterminant éventuellement celle à utiliser en fonction des arguments de la ligne de commande ou des fichiers de configuration. En fait, vous devez sélectionner la bonne usine en sélectionnant la bonne classe dans votre message d'origine. Encore une fois, si vous créez seulement un de quelque chose, vous n'avez pas besoin d'une usine; les usines sont destinées à la production de masse (ou à la création de familles de types concrets apparentés - bien que cela dépasse le cadre de cette question).

(Supposons que nous avons un jeu où l'utilisateur peut sélectionner sur la ligne de commande si pour combattre des robots ou des dragons, nous voudrions instancier un OpponentFactory qui produisent Adversaires (une interface), avec les classes dérivées RobotOpponent et DragonOpponent , et passez cette fabrique à la partie du jeu que spawnsNewOpponent() .De même, un utilisateur peut sélectionner des adversaires courageux ou lâches, que nous aurions mis en place en tant que stratégie.Nous n'avons pas besoin de faire plus d'instances de stratégie, en tant que La stratégie est généralement idempotente (sans état et singleton).)

static int main(String[] args) { 
// setup game world 

final RandomStrategy r = "random".equals(args[0]) 
    ? new PlatformRandom() : new NotSoRandom(Integer.intValue(args[0])) ; 
// notice the simlarity to the code you originally posted; 
// we factored out how to achieve "randomness" as a Strategy. 

// now we will use our Strategy to setup our Factory; 

final ModeFactory f = "test".equals(args[1]) 
    ? new TestFactory(r) : new MainFactory(r); 
// also similar to your code 
// we've just added an extra level of indirection: 
// instead of creating a Mode, we've created an object that can create Modes 
// of the right derived type, on demand. 

// call something that uses our factory 
functionThatRunsameAndNeedstoProduceModesWhenevertNeedsTo(f); 

} 
+0

Alors, comment pourrais-je implémenter ceci dans mon interface graphique? –

0

Votre code pourrait probablement être changé en un modèle d'usine.

Quelque chose comme:

public static Mode createMode(boolean isMainMode) 
{ 
    if(isMainMode) return new MainMode(...); 
    return new TestMode(...); 
} 

place cette méthode quelque part sensible (celui-ci est difficile, peut-être un ModeFactory statique)

Cela suppose que mainmode et TestMode sont sous-types du même type (sous-classes ou mettre en œuvre Interface de mode)

Maintenant tout le jeu doit faire est d'appeler ModeFactory.createMode (...) et passer le booléen approprié.

Modifier (en réponse à la mise à jour OP):

Votre rand() obtient évalué avant que le constructeur réel est appelé, et il présente l'interface graphique. Est-ce cela que vous voulez dire en vous activant?

Vous devez prendre la décision de conception où vous voulez prendre la décision concernant le mode. Si vous avez une interface graphique et que vous avez un modèle, il peut être préférable de concevoir l'interface graphique pour savoir si l'appel à la génération aléatoire (et contextuelle) est nécessaire avant d'appeler la méthode usine, puis passez le nombre aléatoire au méthode d'usine et laissez-le choisir le bon constructeur. L'avoir dans l'autre sens (le modèle appelle votre interface graphique) est plus compliqué et probablement une mauvaise idée.

0

Essayez somthing comme,

abstract class ModeFactory { 

    public static Mode getMode(isMode, numberRanges, numberofGuesses) { 
     return isMode ? new MainMode(numberRanges, numberofGuesses) : new TestMode(numberRanges, numberOfGuesses, randNo()); 
    } 

    public static Mode getMode(isMode, numberRanges, numberofGuesses, someNumber) { 
     return isMode ? new MainMode(numberRanges, numberofGuesses) : new TestMode(numberRanges, numberOfGuesses, someNumber); 
    } 

} 

La classe est abstraite juste pour arrêter initialisation. Vous pouvez le modifier pour utiliser final, puis créer un constructeur privé.

1

Le but d'une Usine est qu'elle ait l'état nécessaire pour créer votre Jeu de manière appropriée.

Je construirais une usine comme ceci:

public class GameFactory { 
    private boolean testMode; 

    public GameFactory(boolean testMode) { 
    this.testMode = testMode; 
    } 

    public Game getGame(int numberRanges, int numberOfGuesses) { 
    return (testMode) ? new MainMode(numberRanges, numberOfGuesses) : 
     new TestMode(numberRanges, numberOfGuesses, getRandom()); 
    } 

    private int getRandom() { 
    . . . // GUI code here 
    } 
} 

Maintenant, vous pouvez initialiser cette usine somwhere dans votre application, et le transmettre pour tout code doit créer un jeu. Ce code n'a plus besoin de s'inquiéter du mode et de passer des paramètres aléatoires supplémentaires - il utilise une interface bien connue pour créer des jeux. Tout l'état nécessaire est internalisé par l'objet GameFactory.

+0

J'ai essayé, mais si je devais ajouter randNo() comme paramètre, j'appellerais le JOptionPane, ce que je ne veux pas. . –

+0

Dans ce cas, ce que vous voulez vraiment, c'est une Usine qui va générer un nombre aléatoire au moment où vous appelez getGame() si testMode est vrai. L'idée est de ne pas faire en sorte que votre code client se soucie du type de jeu qu'il veut créer - ce comportement est entièrement encapsulé par l'usine – levik

+0

Comment pourrais-je alors fournir un JOptionPane? L'idée de randNo() étant dans la classe GUI est parce qu'il appelle le JOptionPane et permet à l'utilisateur d'entrer son propre numéro au lieu d'un nombre aléatoire, juste pour tester le jeu. C'est dans la classe GUI car elle appelle l'interface graphique. –

0
interface ModeFactory { 
    Mode createMode(int numberRanges, int numberOfGuesses); 
} 

class MainModeFactory implements ModeFactory { 
    Mode createMode(int numberRanges, int numberOfGuesses) { 
     return new MainMode(numberRanges, numberOfGuesses); 
    } 
} 

class TestModeFactory implements ModeFactory { 
    Mode createMode(int numberRanges, int numberOfGuesses) { 
     return new TestMode(numberRanges, numberOfGuesses, randNo()); 
    } 
} 

... 

play = modeFactory.createMode(numberRanges, numberOfGuesses); 

donc au démarrage vous cr mangez l'usine de mode appropriée, en la passant à l'endroit où le jeu doit être créé.

0

Très simplement, Utilisez toujours un PARAMETER, dans le cas où le paramètre est utilisé, envoyer null, si vous avez plusieurs paramètres pour d'autres « modes », les encapsuler, en un seul paramètre.

0

Si vous êtes juste après la méthode de l'usine, qui va créer pour vous une classe d'un nom donné essayez ceci:

public static MyInterface createClass(String name) throws IllegalAccessException, 
     InstantiationException, ClassNotFoundException { 
    try { 
     Class myClass = Class.forName(name); 
     MyInterface myObj = (MyInterface) myObj.newInstance(); 
     return myObj; 
    } catch (ClassNotFoundException ex) { 
     logger.error("Could not find a class {}", name); 
     throw ex; 
    } catch (InstantiationException e) { 
     logger.error("Class must be concrete {}", name); 
     throw e; 
    } catch (IllegalAccessException e) { 
     logger.error("Class must have a no-arg constructor {}", name); 
     throw e; 
    } 
} 
0

Qu'est-ce que vous voulez vraiment faire, est de faire une usine, qui vous renvoie un objet de classe abstraite ou d'interface (implémenteurs theyr bien sûr). Dans la méthode d'usine, vous désidez ensuite, quel implémenteur choisir. Si vous choisissez une classe abstraite, vous pouvez implémenter une logique commune et laisser les autres méthodes non implémentées (en les déclarant abstraites).Vous laissez les descendeurs de béton les mettre en œuvre en fonction de leurs besoins. C'est le modèle de conception d'usine:

public class GridManagerFactory { 
    public static AbstractGridManager getGridManager(LifecicleAlgorithmIntrface lifecicleAlgorithm, String... args){ 
     AbstractGridManager manager = null; 

     // input from the command line 
     if(args.length == 2){ 
      CommandLineGridManager clManager = new CommandLineGridManager(); 
      clManager.setWidth(Integer.parseInt(args[0])); 
      clManager.setHeight(Integer.parseInt(args[1])); 
      // possibly more configuration logic 
      ... 
      manager = clManager; 
     } 
     // input from the file 
     else if(args.length == 1){ 
      FileInputGridManager fiManager = new FileInputGridManager(); 
      fiManager.setFilePath(args[0]); 
      // possibly more method calls from abstract class 
      ... 
      manager = fiManager ; 
     } 
     //... more possible concrete implementors 
     else{ 
      manager = new CommandLineGridManager(); 
     } 
     manager.setLifecicleAlgorithm(lifecicleAlgorithm); 
     return manager; 
    } 
} 

La logique de commoun dans la classe abstraite est disponible à ses descendeurs:

public abstract class AbstractGridManager { 
    private LifecicleAlgorithmIntrface lifecicleAlgorithm; 
    // ... more private fields 

    //Method implemented in concrete Manager implementors 
    abstract public Grid initGrid(); 

    //Methods common to all implementors 
    public Grid calculateNextLifecicle(Grid grid){ 
     return this.getLifecicleAlgorithm().calculateNextLifecicle(grid); 
    } 

    public LifecicleAlgorithmIntrface getLifecicleAlgorithm() { 
     return lifecicleAlgorithm; 
    } 
    public void setLifecicleAlgorithm(LifecicleAlgorithmIntrface lifecicleAlgorithm) { 
     this.lifecicleAlgorithm = lifecicleAlgorithm; 
    } 
    // ... more common logic and geter-seter pairs 
} 

implémenteurs béton seulement besoin de mettre en œuvre la méthode qui est déclarée abstraite:

public class FileInputGridManager extends AbstractGridManager { 

     private String filePath; 

     @Override 
     public Grid initGrid() { 
      return this.initGrid(this.getFilePath()); 
     } 

     public Grid initGrid(String filePath) { 
      List<Cell> cells = new ArrayList<>(); 
      char[] chars; 
      File file = new File(filePath); // for ex foo.txt 
      // ... more logic 
      return grid; 
     } 
    } 

Le récepteur de AbstractGridManager appellerait les méthodes sur lui et obtiendrait la logique, implémentée dans les descendeurs concrets (et partiellement dans les méthodes de classes abstraites) avec savoir quelle est la mise en œuvre concrète qu'il a eu. C'est aussi connu comme l'inversion du contrôle ou l'injection de dépendance