views:

152

answers:

3

I am trying to use a factory pattern to create a QuestionTypeFactory where the instantiated classes will be like MultipleChoice, TrueFalseQuestion etc.

The factory code looks something like this

class QuestionFactory {
    public enum QuestionType {
        TrueFalse,
        MultipleChoice,
        Essay
    }

public static Question createQuestion(QuestionType quesType) {
    switch (quesType) {
        case TrueFalse:
            return new TrueFalseQuestion();
        case MultipleChoice:
            return new MultipleChoiceQuestion();
        case Essay:
            return new EssayQuestion();
    }
    throw new IllegalArgumentException("Not recognized.");
}
}

This works ok for now. If I want to add another question type I will need to modify the factory class and I do not want to do that.

How can I set it up so that each question class registers itself with the Factory so that when I add a new question type, I do not have to change the code for the factory? I am a bit new to java and am not sure how to do this.

Edit

Additional Information

All the question classes implement an IQuestion interface. I am looking for a way to implement a method like

public static void registerType(QuestionType quesType, Class<IQuestion> ques)

so that I can call this method from a static block from my classes so that when I add a new question type, I will not have to change or add any code in the Question Factory. I know I would have to change the current implementation to make it generic. I am not sure the method that I wrote above is correct in terms of its arguments syntactically or not but it shows what I want in concept.

A: 

I'm pretty sure you just want to create a method on your enum type which will return the appropriate Question object. So anytime someone adds an enum value to QuestionType they will also have to update the method. Doesn't solve your problem of having to update that method though...

Tim Bender
But presumably, the Factory can decided to create an alternative implementation for a given type (i.e. an implementation that neither the enum, nor the calling code need to know about).
Thilo
+5  A: 

You can probably do that with the register method you've shown, through the Reflection API (that Class thingy).

I am proficient enough with Java Reflection to write a more helpful answer, but if you look for some getConstructor method or something you'll probably get there.

To call that method you should do something like (note the .class syntax):

QuestionFactory.registerType(QuestionType.TrueFalse, TrueFalseQuestion.class);

EDIT Ah, whatever, I have the time to investigate. Try this:

public class QuestionFactory {
    static final Map<QuestionType, Constructor<? extends Question>> map =
        new HashMap<QuestionType, Class<? extends Question>>();

    public static void registerType(QuestionType quesType, Class<? extends Question> ques) {
        map.put(quesType, ques.getConstructor());
    }

    public static Question createQuestion(QuestionType quesType) {
        return map.get(quesType).newInstance();
    }
}

I haven't compiled this, but it should work, or at least guide you in the right direction. For this to work the Question implementations must have a constructor without arguments.

Because you're using a static factory (aka, object-oriented global variables) you can make questions register themselves in their static initializer.

public class TrueFalseQuestion implements Question {
    static {
        QuestionFactory.registerType(QuestionType.TrueFalse, TrueFalseQuestion.class);
    }
    // Whatever else goes here
}
Martinho Fernandes
and where should I call the static register method in the question class?
TP
If you want to remove the dependency from the static factory on the questions, you can do it the other way: have the questions depend on the static factory, i.e, register themselves. Probably in the static initializer.
Martinho Fernandes
that is exactly what I am trying to achieve. To let the questions register themselves. I am not sure where to place the code that registers the question class with the factory.
TP
@TP: Inside a static initialization block in the question class, e.g.: public class TrueFalseQuestion implements Question static { QuestionFactory.registerType(QuestionType.TrueFalse, TrueFalseQuestion.class); }
ig0774
The static block is not run until I actually instantiate a TrueFalseQuestion. How can I work around that?
TP
@TP: Yes, static initializers run when you first use the type. I don't know how you can get around that, other than explicitly touch the types yourself on main, or (maybe) using a special class loader. Maybe you should ask another question for that issue. It will gather more minds. I'm sure someone else will be able help.
Martinho Fernandes
+1  A: 

One possibility:

public enum QuestionType {
    TrueFalse(TrueFalseQuestion.class),
    MultipleChoice(MultipleChoiceQuestion.class),
    Essay(EssayQuestion.class)

    private final Class<? extends Question> implementationType;
    QuestionType(Class<? extends Question> implementationType) {
       this.implementationType = implementationType;
    }

    public Question createQuestion() { 
       return implementationType.newInstance(); 
    }
}

Of course, this gets rid of the factory and assumes all your questions have no-args constructors, but as far as I can tell, it covers all the cases of the code sketch above. If construction for the particularly classes is more complicated, you can always setup something like:

public enum QuestionType {
    TrueFalse { public Question createQuestion() { /* construction logic goes here */ } }
    public abstract Question createQuestion();
}
ig0774
Yes, yes, inside the enum is much better! This is probably the main Java feature I'd love to see in C#.
Martinho Fernandes
But this still doesnt solve the problem of dependency. I would still have to update the enum when I create a new question class. Is there any way to get rid of the dependency with this method?
TP
and how would I use it in the main method to actually create an instance of a particular question type?
TP
@TP: `Question foo = QuestionType.TrueFalse.createQuestion();`.
Martinho Fernandes
@TP: No, this doesn't solve the problem of dependency. The only way to do that is to get rid of the enum using something like what Martinho suggested. For a small number of question types, I think the enum is quite elegant, but for unlimited options, you really need to switch to something a little more verbose.
ig0774