Strategy Design Pattern, Generics and TypeSafety

血红的双手。 提交于 2019-12-04 12:09:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!