问题
I am trying to write a Factory Pattern to create either a MainMode or a TestMode in my program. The code I was previously using to create these objects was:
play = (isMode) ? new MainMode(numberRanges, numberOfGuesses) :
new TestMode(numberRanges, numberOfGuesses, randNo());
My Game (play) would either create a MainMode object or a TestMode object depending on a boolean value (isMode). As you can see I am adding an extra value into my TestMode object (randNo()). This value is used within TestMode to allow the user to input their own "Random Number", whereas within the MainMode constructor this was randomly generated. In this program both MainMode and TestMode are sub-classes of the abstract class Game.
Now I want to replace this line with a Factory Pattern, although I am unsure as my TestMode constructor requires an extra object and I am unsure where I would need to pass this value. If I were going to create a Factory it'd need to be in a new class, probably named GameFactory or ModeFactory or something along those lines.
How would I go about this?
EDIT: The problem here is that the code above is in my GUI, where the values for numberRanges, numberOfGuesses and the randNo() method are. I want to create a Factory class but I am unable to pass these values through because randNo() activates itself. Here is my randNo() method.
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;
}
The problem is that whenever I pass randNo() it displays the JOptionPane. As I've said already the GUI and Logic is separate. The GUI is in a GUI package whilst the rest of the code is in the logic package.
回答1:
Note that some of the other answers may arguably describe factories, but don't describe the GOF Factory Pattern.
Now I want to replace this line with a Factory Pattern, although I am unsure as my TestMode constructor requires an extra object and I am unsure where I would need to pass this value.
Well, you could think of it this way: MainMode, not TestMode, is the one that does a special thing. The special thing it does, is to ignore the given number, in order to ensure it's really random. In this way of thinking about it, it's MainMode that does something extra.
Or, if other than the randomness, MainMode and TestMode are not different, then you'd be thinking perhaps that you can factor out that similarity into one class, which is provided one of two Strategies for calculating random numbers. One Strategy would actually be random, and one would be perverse, with a random range of only 1 value.
But let's assume that there are other differences between MainMode and TestMode -- presumably TestMode outputs extra debugging to System.out or something.
We can still factor out "how do we supply randomness" from are we testing or playing the game for real". These are orthogonal concerns.
So now we know that in addition to whatever else a 'Mode does, it should accept a Randomness Strategy. Then we could, for example, when you're told that the standard platform random isn't really random enough, you can replace it with a better random.
Or you can do testing where the range of randoms is constrained to only two choices, or always alternates from one to zero, or returns on each call the next value in some Vecrtor or Iterator.
So we use the GOF Strategy Pattern to build the randomness strategies:
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(); }
}
Now, if your whole app only ever creates one 'Mode, there's no need for a factory; you use a factory when you need to create the same class type over and over; the Factory is in fact just a Strategy for creating the right kind of (sub) class.
In production code, I've used factories where I have some generic class that creates stuff, and I need to tell how to create the right subclass to create; I pass in a factory to do that.
Now we create a Factory pattern for the 'Mode; this will be surprisingly similar to the Strategy pattern:
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);
}
}
So now you know about the Factory Pattern and Strategy Pattern, and how they're similar in "shape", but different in how they're used: Factory Pattern is Object Creational and returns an object to be used; Strategy is Object Behavioral, and an instance is usually created explicitly and a reference is held to the instance, to encapsulate an algorithm. But in terms of the structure, they're quite similar.
Edit: the OP asks, in a comment, "How would I integrate this into my GUI?"
Well, none of this belongs in the GUI of your program, except possibly the 'Mode. You'd create the ConcreteStrategy and pass it to the preferred Factory in some setup routine, possibly determining which to use based on command line arguments or config files. basically, you'd select the correct factory very much as you selecting the correct class in your original post. Again, if you're only ever creating one of something, you don't need a Factory; factories are for mass production (or creating families of related concrete types -- though that's beyond the scope of this question).
(Assume we have a game where the user can select on the command line whether to fight robots or dragons; then we'd want to instantiate an OpponentFactory that produce Opponents (an interface), with derived classes RobotOpponent and DragonOpponent, and pass that factory to the part of the game that spawnsNewOpponent(). Similarly, a user might select brave or cowardly opponents, which we'd set up as a Strategy. We don't need to make more Strategy instances, as a Strategy is usually idempotent (stateless and 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 );
}
回答2:
The whole point of a Factory is that it should have the needed state to create your Game appropriately.
So I would build a factory like this:
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
}
}
Now you can initialize this factory somwhere in your app, and pass it in to whatever code needs to create a Game. This code now doesn't need to worry about what mode it is, and passing extra random params - it uses a well known interface to create Games. All the needed state is internalized by the GameFactory object.
回答3:
Try somthing like,
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);
}
}
The class is abstract just to stop intialization. You could modify it to use final and then create a private constructor.
回答4:
Your code could probably be changed into a factory pattern.
Something like:
public static Mode createMode(boolean isMainMode)
{
if(isMainMode) return new MainMode(...);
return new TestMode(...);
}
Place this method somewhere sensible (this one is tricky, maybe a static ModeFactory)
This assumes that MainMode and TestMode are subtypes of the same type (subclasses or implement Mode interface)
Now all play has to do is call ModeFactory.createMode(...) and pass the appropriate boolean.
Edit (in response to OP update):
Your rand() gets evaluated before the actual constructor is called, and it presents the GUI. Is that what you mean by activating itself?
You have to make the design decision where you want to make the decision about the mode. If you have a GUI and you have a model, it might be preferable to design the GUI to know whether or not the call to random generation (and popup) is necessary before you call the factory method, and then pass the random number to the factory method and let it just pick the correct constructor.
Having it the other way around (model calls your GUI) is trickier and probably a bad idea.
回答5:
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);
So at startup you create the appropriate mode factory, passing it in to wherever the play needs to be created.
回答6:
Very simply, ALWAYS USE A PARAMETER, in case the parameter is not used, send null
, if you have several parameters for other "Modes", encapsulate them, into a single parameter.
回答7:
If you are just after factory method, that will create for you a class of a given name try this:
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;
}
}
回答8:
What you realy want to do, is make a factory, which returns you an object of abstract class or interface (theyr implementors of course). In the factory method you then deside, which implementor to choose. You if you choose an abstract class, you can implement some common logic in it and let other methods unimplemented (declaring them abstract). You would let the concrete descenders implement them depending on theyr need. This is factory design pattern:
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;
}
}
The commoun logic in the abstract class is available to its descenders:
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
}
The concrete implementor only need implement the method which is declared 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;
}
}
The receiver of AbstractGridManager would call the methods on him and get the logic, implemented in the concrete descenders (and partually in the abstract class methods) without knowing what is the concrete implementation he got. This is also know like inversion of control or dependency injection
来源:https://stackoverflow.com/questions/708336/best-way-to-implement-the-factory-pattern-in-java