问题
I want to create the following Strategy Pattern combined with Factory, but I want it to be typesafe. I have done the following till now:
public interface Parser<T> {
public Collection<T> parse(ResultSet resultSet);
}
public class AParser implements Parser<String> {
@Override
public Collection<String> parse(ResultSet resultSet) {
//perform parsing, get collection
Collection<String> cl = performParsing(resultSet); //local private method
return cl;
}
}
public class ParserFactory {
public enum ParserType {
APARSER
}
public static <T> Parser<T> createParser(ParserType parserType) {
Parser<?> parser = null;
switch (parserType) {
case APARSER:
parser = new AParser();
break;
}
//unchecked cast happens here
return (Parser<T>) parser;
}
}
public class Context {
public <T> Collection<T> getResults(String query, ParserType parserType) {
ResultSet resultSet() = getResultSet(query); //local private method
Parser p = ParserFactory.createParser(parserType);
Collection<T> results = p.parse(resultSet)
}
}
In general whatever I attempt, somewhere I will have an unchecked cast. Anyone have an idea how I can refactor the code to be typesafe?
Checking Effective Java I also stumbled upon this pattern:
public final class ParserFactory {
private ParserFactory() {
}
private static class AParser implements Parser<String> {
@Override
public Collection<String> parse(ResultSet resultSet) {
//...
return new ArrayList<>();
}
}
public static final Parser<String> APARSER = new AParser();
}
So now I can use as Ingo suggested
public <T> Collection<T> getResults(String query, Parser<T> p)
as
getResults("query", ParserFactory.APARSER);
Or would this be better with enums?
回答1:
I'd simply pass a Parser<T>
to the getResults()
method and forget about that factory stuff. Look, if you say:
public <T> Parser<T> createParser(ParserType typ) { ... }
you are promising that the method will create a parser of any type the caller wants. This is only possible in a type safe way with parsers that all return an empty collection. Moreover, you can't return a Parser<String>
from that function, because String
is not the same as any type the caller wanted.
If, however, you write:
public <T> Collection<T> getResults(String query, Parser<T> parser) {
ResultSet resultSet = getResultSet(query); //local private method
Collection<T> results = parser.parse(resultSet);
return results;
}
you have exactly what you wanted: the getResult
method is independent of how the parser works, and yet it returns a collection of the correct type.
And later, instead of
Collection<String> it = (Collection<String>) getResults("query", APARSER);
you say:
Collection<String> it = getResults("query", new AParser());
This is sound and makes sense.
回答2:
I usually use this format. I know that a lot of people does not like it but no one suggested a better approach so far.
public enum ParserType {
APARSER(new AParser());
private Parser parser; // this should be an interface which is implemented by AParser
private ParseType(Parser parser){
this.parser = parser;
}
public Parser getParserInstance() {
return parser;
}
}
You can pass Class
objects around if you want a new instance every time:
public enum ParserType {
APARSER(AParser.class);
private Class<Parser> parserClass;
private ParseType(Class<Parser> parserClass){
this.parserClass = parserClass;
}
public Parser createParser() {
return parserClass.newInstance(); // TODO: handle exceptions here
}
}
Note: I'm eager to find a better approach so if you have some thoughts please share them in a comment.
回答3:
I applaud your desire to use the Strategy Pattern; +1 for that. I do think Ingo's comments are spot on.
Just an additional comment (taken from Effective Java, 2nd Ed.):
switch (parserType) {
case APARSER:
parser = new AParser();
break;
}
To use Joshua Bloch's words, "this solution appears compact and even elegant." However it may also be fragile and difficult to maintain. In general, you should try not to switch on enum constants because whenever you do so, your code will break whenever you change the enum.
This is exactly the right time to use an abstract method definition in your enum and place the desired code right there with the enum constant. Doing this guarantees that you never forget to add the needed enum associated code to your project and assures that whenever you add to your enum that your project won't break.
In fact, if your goals allow for it, it may even be possible or adviseable to move the entire factory method to your enum and have your enum class implement the Strategy interface.
来源:https://stackoverflow.com/questions/19908963/strategy-design-pattern-generics-and-typesafety