I am trying to refactor some code in order to reduce code duplication. Currently I have a service that takes its own custom Payload class and returns its own Response class. And currently it is not one service class that does this but around 5 or 6. So that makes for a LOT of code duplication even though the body of the methods are literally the same, just the return type is different.
For example:
public class ServiceA {
ResponseA getResponse(PayloadA){...}
}
public class ServiceB {
ResponseB getResponse(PayloadB){...}
}
public class ServiceC {
ResponseC getResponse(PayloadC){...}
}
Where the method bodies are literally word for word the same but because it is a post request using WebClient where the methods end with ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(response, new TypeReference<>() {});
, we are just copying the method again and again.
I tried to implement an abstract superclass called RestService and have two empty interfaces called RestResponse and RestPayload that each service can implement on their own but then how do I get the service class to return it's own Response type and not the RestResponse type which is just an empty interface?
EDIT - UPDATE: So on a somewhat related note I solved my problem by doing the following:
public abstract class ParentService<R extends IResponse, P extends IPayload>{
public abstract R getPayload(P);
}
public class ServiceA extends ParentService<ResponseA, PayloadA> {
@Override
public ResponseA getPayload(PayloadA){
/***/
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(response, new TypeReference<>() {});
}
}
Where each additional service is similarly inherited. And each response and payload implement the IResponse and IPayload interface models respectively.
Thank you for your suggestions!
(This final design decision was based on the current accepted answer, additional reading on generics and abstract classes in java as well as codebase design decisions)
Would something like this work for you?
T
will return the type that the caller of the method wants.public class GeneralService {
public <T> T getResponse(Object payload, Class<T> clazz){
// do sth with payload
return mapper.readValue(response, clazz);
}
}
In case you want to be able to deserialize Collections as well, you can use TypeReference
instead of Class
:
public class GeneralService {
public <T> T getResponse(Object payload, TypeReference<T> type){
// do sth with payload
return mapper.readValue(response, type);
}
}
and then call it with the type, for example for list of strings:
generalService.getResponse(payload, new TypeReference<List<String>>(){})