ajay chauhan
ajay chauhan

Reputation: 89

Factory Pattern when the output type is different in java

I am fairly new to java and below are my mapper interface definitions:

public interface CourseMapper {
    Course map(JsonObject courseJson);
    List<Course> map(JsonArray coursesJson);
}

public interface LocationMapper {
    Location map(JsonObject locationJson);
    List<Location> map(JsonArray locationsJson);
}

and below are the two classes:

public interface Location {
    Long id();
    String name();
    Location mutate(Long id);
}

public interface Course {
    Long id();
    String name();
    Course mutate(Long id);
}

What I need is based on a key(String), I need to invoke the respective map() function of respective Mapper. Example if the key=="location", I need to invoke LocationMapper and if the key=="course", I need to invoke CourseMapper (for gson Mapper)...... and so on.

I tried using Factory Pattern for the same, but it seems that the same doesn't work when the Output is of different type. Would anyone know how to achieve the same without overtly complicating with if-else ladder.

Upvotes: 1

Views: 304

Answers (1)

Jiri Tousek
Jiri Tousek

Reputation: 12440

The most simple approach is to have a Map<String, Object> to store your mappers in:

private Map<String, Object> mappers;

public <T> T getMapper(String key, Class<T> mapperType) {
    Object mapper = mappers.get(key);
    // code for when mapper == null
    if (mapperType.isAssignableFrom(mapper)) {
        return (T) mapper;
    } else {
         // code for when mapper is of wrong type
    }
}

As you'll notice however, this gives up static type checks, so any type mismatch will only be found at run time.


A more complicated approach can save (sort of) the static type checks. It basically ties the result type with the string key:

private static class MapperKey<T> {
    private final String key;

    public MapperKey(String key) {
        this.key = key;
    }
}

private Map<String, Object> mappers;

public static final MapperKey<CourseMapper> COURSE_KEY = new MapperKey<CourseMapper>("course");

public <T> void addMapper(MapperKey<T> mapperKey, T mapper) {
    mappers.put(mapperKey.key, mapper);
}

public <T> T getMapper(MapperKey<T> mapperKey) {
    Object mapper = mappers.get(mapperKey.key);
    // code for when mapper == null
    return (T) mapper;
}

This approach however depends on users using the predefined constants for MapperKeys, or creating the correct keys (correct type parameter for the respective key).


And finally, if you can replace your CourseMapper and LocationMapper interfaces with this:

public interface Mapper<T> {
    T map(JsonObject courseJson);
    List<T> map(JsonArray coursesJson);
}

And if you only need one mapper per output type, you can use the output type as a key:

private Map<Class<?>, Mapper<?>> mappers;

public <T> void addMapper(Class<T> outputType, Mapper<T> mapper) {
    mappers.put(outputType, mapper);
}

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> outputType) {
    return (T) mappers.get(outputType);
}

Upvotes: 3

Related Questions