Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strategy Design Pattern, Generics and TypeSafety

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?

like image 726
ChrisGeo Avatar asked Nov 11 '13 14:11

ChrisGeo


2 Answers

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.

like image 155
Ingo Avatar answered Sep 23 '22 09:09

Ingo


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.

like image 29
Adam Arold Avatar answered Sep 23 '22 09:09

Adam Arold