2009-04-02 22 views
5

Estoy tratando de escribir un patrón de fábrica para crear un modo principal o un modo de prueba en mi programa. El código que estaba usando anteriormente para crear estos objetos era:Principiante: Patrón de fábrica en Java

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

mi juego (juego) sería o bien crear un objeto o un objeto MainMode TestMode en función de un valor booleano (isMode). Como puede ver, agrego un valor extra a mi objeto TestMode (randNo()). Este valor se usa dentro de TestMode para permitir que el usuario ingrese su propio "Número aleatorio", mientras que dentro del constructor MainMode esto se generó al azar. En este programa, tanto MainMode como TestMode son subclases de la clase abstracta Game.

Ahora quiero reemplazar esta línea con un patrón de fábrica, aunque no estoy seguro ya que mi constructor TestMode requiere un objeto adicional y no estoy seguro de dónde necesitaría pasar este valor. Si fuera a crear una fábrica, necesitaría estar en una nueva clase, probablemente llamada GameFactory o ModeFactory o algo así.

¿Cómo podría hacerlo?

EDIT: El problema aquí es que el código anterior está en mi GUI, donde están los valores para numberRanges, numberOfGuesses y el método randNo(). Quiero crear una clase Factory pero no puedo pasar estos valores porque randNo() se activa solo. Aquí está mi método 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; 
} 

El problema es que cada vez que paso randNo() muestra JOptionPane. Como ya he dicho, la GUI y la lógica están separadas. La GUI está en un paquete GUI mientras que el resto del código está en el paquete lógico.

Respuesta

12

Tenga en cuenta que algunas de las otras respuestas pueden describir fábricas, pero no describen GOF Factory Pattern.

Ahora quiero reemplazar esta línea con un patrón fábrica, aunque estoy seguro de que mi constructor TestMode requiere una objeto extra y estoy seguro de que me tendría que pasar este valor.

Bueno, podría pensarlo de esta manera: MainMode, no TestMode, es el que hace algo especial. Lo especial que hace es ignorar el número dado, para garantizar que sea realmente aleatorio. De esta manera de pensarlo, es MainMode el que hace algo extra.

O, si además de la aleatoriedad, MainMode y TestMode no son diferentes, entonces estaría pensando quizás que puede restar importancia a esa similitud en una clase, que se proporciona una de dos estrategias para calcular números aleatorios. Una estrategia sería en realidad aleatoria, y una sería perversa, con un rango aleatorio de solo 1 valor.

Pero supongamos que existen otras diferencias entre MainMode y TestMode: presumiblemente TestMode genera una depuración adicional a System.out o algo así.

Todavía podemos factorizar "¿cómo suministramos aleatoriedad" de que estamos probando o jugar el juego de verdad". Estos son ortogonales preocupaciones.

Así que ahora sabemos que, además de cualquier otra cosa que una 'Modo sí, debería aceptar una estrategia de aleatoriedad. Entonces podríamos, por ejemplo, cuando te digan que la plataforma estándar aleatoria no es lo suficientemente aleatoria, puedes reemplazarla por una mejor aleatoria.

O tú puede hacer pruebas donde el rango de randoms está restringido a solo dos opciones, o siempre alterna de uno a cero, o devuelve en cada llamada el siguiente valor en algún Vecrtor o Iterat o.

por lo que utilizar el patrón de estrategia GOF para construir las estrategias de aleatoriedad:

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(); } 
} 

Ahora, si todo tu aplicación sólo crea siempre una Mode', no hay necesidad de una fábrica; usa una fábrica cuando necesita crear el mismo tipo de clase una y otra vez; la fábrica es, de hecho, solo una estrategia para crear el tipo correcto de (sub) clase.

En el código de producción, he usado fábricas donde tengo una clase genérica que crea cosas, y necesito decir cómo crear la subclase correcta para crear; Paso en una fábrica para hacer eso.

Ahora creamos un patrón de Fábrica para el 'Modo; esto va a ser sorprendentemente similar al patrón de la estrategia:

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); 
    } 
} 

Así que ya saben acerca del patrón de la fábrica y el patrón de estrategia, y cómo son similares en "forma", pero diferentes en cómo se usan: Fábrica El patrón es Object Creational y devuelve un objeto para ser utilizado; La estrategia es Object Behavioral, y una instancia generalmente se crea explícitamente y se mantiene una referencia a la instancia, para encapsular un algoritmo. Pero en términos de la estructura, son bastante similares.

Editar: el OP pregunta, en un comentario, "¿Cómo podría integrar esto en mi GUI?"

Bueno, nada de esto pertenece a la GUI de su programa, excepto posiblemente el 'Modo'. Debería crear ConcreteStrategy y pasarlo a la Factory preferida en alguna rutina de configuración, posiblemente determinando cuál usar según los argumentos de la línea de comando o los archivos de configuración. básicamente, seleccionarías la fábrica correcta en la misma medida que seleccionas la clase correcta en tu publicación original. Nuevamente, si solo creas algo, no necesitas una fábrica; las fábricas son para producción en masa (o para crear familias de tipos de concreto relacionados, aunque eso está más allá del alcance de esta pregunta).

(Supongamos que tenemos un juego donde el usuario puede seleccionar en la línea de comandos si luchar contra robots o dragones, entonces quisiéramos crear una OpponentFactory que produzca oponentes (una interfaz), con clases derivadas RobotOpponent y DragonOpponent , y pasar esa fábrica a la parte del juego que engendra aNewOpponent(). De forma similar, un usuario puede seleccionar oponentes valientes o cobardes, que nosotros configuraríamos como una Estrategia. No necesitamos hacer más instancias de Estrategia, como estrategia se suele idempotente (sin estado y 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

Entonces, ¿cómo implementaría esto en mi GUI? –

0

Su código probablemente podría cambiarse a un patrón de fábrica.

Algo así como:

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

lugar este método en alguna parte sensible (éste es complicado, tal vez un ModeFactory estática)

Esto supone que MainMode y TestMode son subtipos del mismo tipo (subclases o implementan Interfaz de modo)

Ahora todo lo que tiene que hacer es llamar a ModeFactory.createMode (...) y pasar el booleano apropiado.

Editar (en respuesta a la actualización OP):

Su rand() se evalúa antes que el constructor real se llama, y ​​presenta la interfaz gráfica de usuario. ¿Es eso lo que quieres decir al activarse?

Tiene que tomar la decisión de diseño en la que desea tomar la decisión sobre el modo. Si tiene una GUI y tiene un modelo, puede ser preferible diseñar la GUI para saber si es necesaria o no la generación aleatoria (y la ventana emergente) antes de llamar al método de fábrica, y luego pasar el número aleatorio a la método de fábrica y deja que solo elija el constructor correcto.

Tenerlo al revés (el modelo llama a su GUI) es más complicado y probablemente sea una mala idea.

0

Probar algo como,

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 clase abstracta es sólo para detener la inicialización. Puede modificarlo para usar final y luego crear un constructor privado.

1

El objetivo de una fábrica es que debería tener el estado necesario para crear su juego de forma adecuada.

Así que me gustaría construir una fábrica de la siguiente manera:

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 
    } 
} 

Ahora puede inicializar esta fábrica somwhere en su aplicación, y pasarlo a cualquier código que necesita para crear un juego. Este código ahora no necesita preocuparse por qué modo es, y pasar parámetros aleatorios adicionales: utiliza una interfaz bien conocida para crear juegos. Todo el estado necesario es internalizado por el objeto GameFactory.

+0

le he dado una oportunidad, pero si tuviera que agregar randNo() como parámetro entonces sería llamar a la JOptionPane, que es lo que no quiero . –

+0

En este caso, lo que realmente desea es una fábrica que generará un número aleatorio en el momento de invocar getGame() si testMode es verdadero. La idea es que el código de su cliente no se preocupe por el tipo de juego que desea crear; ese comportamiento está encapsulado por la fábrica – levik

+0

¿Cómo podría entonces proporcionar un JOptionPane? La idea de que randNo() esté en la clase de GUI es porque llama a JOptionPane y permite al usuario ingresar su propio número en lugar de un número aleatorio, solo para probar el juego. Esto está en la clase de GUI porque llama la GUI. –

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); 

Así que en el arranque CR Elija la fábrica de modo apropiado, pasándola a donde se necesite crear la obra.

0

Muy simplemente, utilizar siempre un PARÁMETROS, en caso de que no se utiliza el parámetro, envía null, si tiene varios parámetros para otros "Modos", encapsular, en un solo parámetro.

0

Si se acaba después de método de fábrica, que va a crear para usted una clase de un nombre dado de probar esto:

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

Lo que se quiere realmente que hacer, es hacer una fábrica, que devuelve un objeto de clase o interfaz abstracta (sus implementadores, por supuesto). En el método de fábrica, entonces usted desea, qué implementador elegir. Si eliges una clase abstracta, puedes implementar una lógica común en ella y dejar que otros métodos no se implementen (declarándolos abstractos).Permitiría que los descensores concretos los implementen según su necesidad. Este es el patrón de diseño de fábrica:

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 lógica comunes disponen en la clase abstracta está disponible para sus trazos descendentes:

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 
} 

El implementador concreto sólo es necesario poner en práctica el método que se declara abstracta:

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; 
     } 
    } 

El receptor de AbstractGridManager llamaría a los métodos sobre él y obtendría la lógica, implementada en los descendientes concretos (y parcialmente en los métodos de clase abstracta) con sabiendo cuál es la implementación concreta que obtuvo. Esto también se conoce como inversión de control o la inyección de dependencias

Cuestiones relacionadas