2009-04-02 16 views
5

Sto provando a scrivere un modello di fabbrica per creare un MainMode o un TestMode nel mio programma. Il codice che è stato precedentemente utilizzato per creare questi oggetti era:Principiante: Modello di fabbrica in Java

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

My Game (gioco) sarebbe creare un oggetto o un oggetto di MainMode TestMode in funzione di un valore booleano (isMode). Come puoi vedere, aggiungo un valore aggiuntivo al mio oggetto TestMode (randNo()). Questo valore viene utilizzato all'interno di TestMode per consentire all'utente di inserire il proprio "Numero casuale", mentre all'interno del costruttore MainMode questo è stato generato casualmente. In questo programma sia MainMode che TestMode sono sottoclassi della classe astratta Game.

Ora voglio sostituire questa linea con un modello di fabbrica, anche se non sono sicuro poiché il mio costruttore TestMode richiede un oggetto aggiuntivo e non sono sicuro dove avrei bisogno di passare questo valore. Se avessi intenzione di creare una Factory, avrebbe dovuto trovarsi in una nuova classe, probabilmente chiamata GameFactory o ModeFactory o qualcosa del genere.

Come dovrei fare questo?

MODIFICA: Il problema qui è che il codice precedente è nella GUI, dove sono i valori per numberRanges, numberOfGuesses e il metodo randNo(). Voglio creare una classe Factory ma non riesco a passare questi valori perché randNo() si attiva da solo. Ecco il mio metodo 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; 
} 

Il problema è che ogni volta che passo randNo() visualizza il JOptionPane. Come ho già detto, la GUI e la logica sono separati. La GUI si trova in un pacchetto GUI mentre il resto del codice si trova nel pacchetto logico.

risposta

12

Si noti che alcune delle altre risposte potrebbero probabilmente descrivere le fabbriche, ma non descrivere il modello di fabbrica GOF.

Ora voglio sostituire questa linea con un modello fabbrica, anche se non sono sicuro come il mio costruttore TestMode richiede una oggetto in più e non sono sicuro dove mi avrei bisogno di passare questo valore.

Bene, si potrebbe pensare in questo modo: MainMode, non TestMode, è quello che fa una cosa speciale. La cosa speciale che fa, è di ignorare il numero dato, al fine di garantire che sia davvero casuale. In questo modo di pensarci, è MainMode che fa qualcosa in più.

Oppure, se diverso da casualità, MainMode e TestMode non sono diversi, allora si potrebbe pensare che è possibile scomporre tale somiglianza in una classe, a cui viene fornita una delle due Strategie per il calcolo di numeri casuali. Una strategia sarebbe in realtà casuale, e uno sarebbe perverso, con un intervallo casuale di solo 1 valore.

Ma supponiamo che ci siano altre differenze tra MainMode e TestMode - presumibilmente TestMode emette un debugging extra su System.out o qualcosa del genere.

Possiamo ancora fattore fuori "come facciamo forniamo casualità" dal stiamo testando o il gioco per davvero". Queste sono ortogonali preoccupazioni.

Così ora sappiamo che, oltre a qualsiasi altra cosa un "La modalità sì, dovrebbe accettare una strategia di casualità.Poi, ad esempio, quando ti viene detto che la piattaforma standard casuale non è abbastanza casuale, puoi sostituirla con una migliore casuale.

Oppure può fare test dove l'intervallo di randoms è vincolato a due sole scelte, o sempre si alterna da uno a zero, o restituisce su ogni chiamata il valore successivo in qualche Vecrtor o Iterat o.

in modo da utilizzare il modello di strategia CdF per costruire le strategie di casualità:

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

Ora, se tutta la vostra applicazione sempre e solo crea uno Mode', non c'è bisogno di una fabbrica; si utilizza una factory quando è necessario creare ripetutamente lo stesso tipo di classe; la fabbrica è in realtà solo una strategia per creare il giusto tipo di (sotto) classe.

Nel codice di produzione, ho utilizzato le fabbriche in cui ho una classe generica che crea elementi e devo dire come creare la sottoclasse giusta da creare; Passo in una fabbrica per farlo.

Ora creiamo un modello di fabbrica per la 'Modalità; questo sarà sorprendentemente simile al modello di strategia:

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

Così ora si sa circa il modello di fabbrica e Pattern strategia, e in che modo sono simili in "forma", ma diverso nel modo in cui siamo abituati: Fabbrica Pattern è Object Creation e restituisce un oggetto da utilizzare; La strategia è Object Behavioral e un'istanza viene solitamente creata in modo esplicito e un riferimento all'istanza, per incapsulare un algoritmo. Ma in termini di struttura, sono abbastanza simili.

Modifica: l'OP chiede, in un commento, "Come dovrei integrare questo nella mia GUI?"

Bene, nessuno di questi elementi appartiene alla GUI del programma, tranne forse la "Modalità". Dovresti creare ConcreteStrategy e passarlo alla Factory preferita in alcune routine di setup, possibilmente determinando quale usare in base agli argomenti della riga di comando o ai file di configurazione. in pratica, dovresti selezionare lo stabilimento corretto molto quando selezioni la classe corretta nel tuo post originale. Anche in questo caso, se crei solo uno di questi elementi, non hai bisogno di una Factory; le fabbriche sono destinate alla produzione di massa (o alla creazione di famiglie di tipi concreti correlati - sebbene ciò esuli dallo scopo di questa domanda).

(Supponiamo di avere un gioco in cui l'utente può selezionare sulla riga di comando se combattere robot o draghi, poi vorremmo creare un'istanza di un OpponentFactory che producono avversari (un'interfaccia), con classi derivate RobotOpponent e DragonOpponent e passa quella fabbrica alla parte del gioco che genera NuewOpponent(). Allo stesso modo, un utente potrebbe selezionare avversari coraggiosi o codardi, che avremmo impostato come strategia. Non abbiamo bisogno di creare più istanze di Strategia, come strategia di solito è idempotente (senza stato e 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

Quindi, come dovrei implementarlo nella mia GUI? –

0

Probabilmente il codice potrebbe essere modificato in un modello di fabbrica.

Qualcosa di simile:

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

Luogo questo metodo da qualche parte sensibile (questo è difficile, forse un ModeFactory statica)

Questo presuppone che MainMode e TestMode sono sottotipi dello stesso tipo (sottoclassi o implementano Interfaccia modalità)

Ora tutto ciò che deve fare è chiamare ModeFactory.createMode (...) e passare il valore booleano appropriato.

Edit (in risposta ad aggiornamento OP):

tuo rand() viene valutato prima il costruttore vero e proprio si chiama, e presenta l'interfaccia grafica. È questo che intendi attivando se stesso?

È necessario prendere la decisione di progettazione in cui si desidera prendere la decisione sulla modalità. Se si dispone di una GUI e si dispone di un modello, potrebbe essere preferibile progettare la GUI per sapere se la chiamata alla generazione casuale (e popup) è necessaria prima di chiamare il metodo factory e quindi passare il numero casuale al metodo di produzione e lascia che scelga il costruttore corretto.

Avere il contrario (il modello chiama la GUI) è più difficile e probabilmente una cattiva idea.

0

Prova somthing come,

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 è astratta solo per fermare intialization. È possibile modificarlo per utilizzare finale e quindi creare un costruttore privato.

1

L'intero punto di una Fabbrica è che dovrebbe avere lo stato necessario per creare il Gioco in modo appropriato.

Quindi vorrei costruire una fabbrica come questa:

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

Ora è possibile inizializzare questa fabbrica somwhere nella vostra applicazione, e passarlo a qualsiasi codice ha bisogno di creare un gioco. Questo codice ora non ha bisogno di preoccuparsi di quale modalità sia, e passando params random extra - utilizza un'interfaccia ben nota per creare giochi. Tutto lo stato necessario è interiorizzato dall'oggetto GameFactory.

+0

Ho dato questo una prova, ma se dovessi aggiungere randNo() come parametro allora chiamerebbe il JOptionPane, che è quello che non voglio . –

+0

In questo caso, quello che vuoi veramente è una Factory che genererà un numero casuale nel momento in cui invochi getGame() se testMode è true. L'idea è di non fare in modo che il codice cliente si preoccupi del tipo di gioco che vuole creare - che il comportamento è tutto incapsulato dalla fabbrica – levik

+0

Come potrei quindi essere in grado di fornire un JOptionPane? L'idea di randNo() nella classe GUI è perché chiama il JOptionPane e permette all'utente di inserire il proprio numero invece di un numero casuale, solo per testare il gioco. Questo è nella classe GUI perché richiama 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); 

Quindi all'avvio si CR mangia la fabbrica in modalità appropriata, trasferendola ovunque sia necessario creare il gioco.

0

Molto semplicemente, SEMPRE utilizzare un parametro, nel caso in cui il parametro non viene utilizzato, inviare null, se si dispone di diversi parametri per gli altri "Modalità", incapsulare loro, in un unico parametro.

0

Se sono solo dopo il metodo factory, che creerà per voi una classe di un determinato nome di provare questo:

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

Quello che davvero vuoi fare, è fare una fabbrica, che si restituisce un oggetto di classe astratta o interfaccia (i loro implementatori ovviamente). Nel metodo factory, quindi, deside, quale implementatore scegliere. Se si sceglie una classe astratta, è possibile implementare una logica comune e lasciare che altri metodi non siano implementati (dichiarandoli astratti).Faresti in modo che i discendenti concreti li implementassero a seconda delle loro necessità. Questo è il modello di progettazione di fabbrica:

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 logica commoun nella classe astratta è a disposizione dei suoi discendenti:

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 
} 

implementor concreta solo bisogno di implementare il metodo che viene dichiarata abstract:

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

Il ricevitore di AbstractGridManager chiamerebbe i metodi su di lui e otterrebbe la logica, implementata nei discendenti concreti (e in parte nei metodi astratti di classe) con fuori sapendo qual è l'implementazione concreta che ha ottenuto. Questo è anche noto come inversione di controllo o dipendenza iniezione

Problemi correlati