I am new to design patterns, and thus have a limited knowledge of what all is available. I am trying to implement a solution for a problem and request the user community to give some guidance on what design pattern to use and how it should be implemented.
Task : Implement central mapping factory that takes some input x and converts it to some output y. New implementation can be added in future.
So far I have implemented factory pattern but I am doubtful on following points:
Is this the correct approach to the problem or am I following any anti-pattern in the implementation
public interface MappingUtil<X, Y> {
Y mapData (X value);
}
@Component
public class A implements MappingUtil<Input A, OutputA> {
@Override
public OutputA mapData(Input A){
// Perform some logic and convert InputA to OutputA and return Output A object
}
}
@Component
public class B implements MappingUtil<Input B, OutputB> {
@Override
public OutputB mapData(Input B){
// Perform some logic and convert InputB to OutputB and return OutputB object
}
}
@Component
public class C implements MappingUtil<Input C, OutputC> {
@Override
public OutputC mapData(Input C){
// Perform some logic and convert InputC to OutputC and return OutputC object
}
}
}
Factory Class
@Component
public class MapperFactory {
private final static Logger LOGGER = LoggerFactory.getLogger(MapperFactory.class);
private static Map<String, MappingUtil> mapperStrategyMapping;
private final A a;
private final B b;
private final C c;
@PostConstruct
void init() {
mapperStrategyMapping = new HashMap<>();
mapperStrategyMapping.put("A", a);
mapperStrategyMapping.put("B", b);
mapperStrategyMapping.put("C", c);
}
@Autowired
MapperFactory(A a,
B b,
C c) {
this.a = a;
this.b = b;
this.c = c;
}
public MappingUtil getMappingUtil(String mapperCode) throws Exception {
if (mapperCode != null) {
return mapperStrategyMapping.get(mapperCode);
} else {
LOGGER.error("Empty mapper code received");
throw new Exception("Empty mapper code");
}
}
Usage of Factory :
@Service
public class XyzImpl implements Xyz{
private final MapperFactory mapperFactory;
@Autowired
XyzImpl(MapperFactory mapperFactory){
this.mapperFactory = mapperFactory;
}
public void doFunction1(Input a , Input b){
OutputA outputA = (Output A) mapperFactory.getMappingUtils("A").mapData(a);
OutputB outputB = (Output B) mapperFactory.getMappingUtils("B").mapData(b);
// Perform some logic on Output A Output B
}
public void doFunction2(Input c){
OutputC outputC = (Output C) mapperFactory.getMappingUtils("C").mapData(c);
// Perform some logic on Output C
}
}
I cannot see anything clearly wrong, but I'll keep looking. My initial suggestion would be: why instead of reinventing the wheel with MappingUtil
you just don't use java.util.Function
(https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/function/Function.html). It's a functional interface that's doing exactly what you need, getting a value of type T
and returning a type R
.
You code for one implementation would look like:
@Component
public class A implements Function<InputA, OutputA> {
@Override
public OutputA apply(InputA input){
// Perform some logic and convert InputA to OutputA and return Output A object
}
}
In your MapperFactory
the field would be:
private static Map<String, Function> mapperStrategyMapping;
You could also simplify your getMappingUtil
as follow:
public Function getMappingUtil(String mapperCode) {
return Optional.ofNullable(mapperCode)
.map(mCode -> mapperStrategyMapping.get(mCode))
.orElseThrow(() -> new IllegalArgumentException("Empty mapper code received"));
}
To avoid the cast, which I don't like either I cannot see many options, one option could be setting a boundary in terms of superclass for the return type of your Function
, but maybe this is outside the scope of your exercise.