I'm sorry for the long question but bear with me, I have tried to make my problem as understandable as possible. If you believe it can be more concise feel free to edit it.
I have a client-server system where the client sends different types of requests to the server, and based on the request, gets back a response.
The code in the client system is:
int requestTypeA() {
Request request = new Request(TypeA);
Response response = request.execute();
// response for request of TypeA contains a int
return response.getIntResponse();
}
String requestTypeB() {
Request request = new Request(TypeB);
Response response = request.execute();
// response for request of TypeB contains a String
return response.getStringResponse();
}
For the above code to operate correctly the Request
class is:
class Request {
Type type;
Request(Type type) {
this.type = type;
}
Response execute() {
if (type == TypeA) {
// do stuff
return new Response(someInt);
}
else if (type == TypeB) {
// do stuff
return new Response("someString");
}
else if ...
}
}
and Response
is like this:
class Response {
int someInt;
String someString;
Response(int someInt) {
this.someInt = someInt;
}
Response(String someString) {
this.someString = someString;
}
int getIntResponse() {
return someInt;
}
String getStringResponse() {
return someString;
}
}
The above solution has two problems:
execute
method is going to be full of if
, else if
blocks.someString
is not initialized, e.g. it got confused with response for request of Type A.About the first problem a solution I came up with is with the use of polymorphism. So have a parent class Request
and for every type of request have a subclass of Request
, so have a RequestTypeA
and RequestTypeB
. All of the classes override the execute
method.
About the 2. problem I've only one possible idea on how to solve it: Similarly to Request
create subclasses of Response
based on the response and have something like this.
interface Response {
}
class ResponseTypeA {
ResponseTypeA(int i) { ... }
int getIntResponse() { ... }
}
class ResponseTypeB {
ResponseTypeB(String s) { ... verify s is valid ... }
String getStringResponse() { ... }
}
Now I can be sure that if a response if of type ResponseTypeB
it's going to contain a valid string. And I can write the client code as follows:
String requestTypeB() {
Request request = new Request(TypeB);
ResponseTypeB response = (ResponseTypeB) request.execute();
return response.getStringResponse();
}
and now I'm obliged to type cast the return type of execute
.
My main question/problem is: Is there a way to avoid type casting in the above case? Or if you are aware of a better solution (design pattern?) for the above problem?
They're polymorphic. By the way, all Java objects are polymorphic because each object is an Object at least. We can assign an instance of Animal to the reference variable of Object type and the compiler won't complain: Object object = new Animal();
Typecasting, or type conversion, is the process of assigning a primitive data type's value to another primitive data type. Programmers need to check the compatibility of the data type they are assigning to another data type, in advance.
For Example, in java, the numeric data types are compatible with each other but no automatic conversion is supported from numeric type to char or boolean. Also, char and boolean are not compatible with each other.
Trying to separate the request from the response is futile. They are bound together by the API - R r = f(Q)
.
You have a RequestA
that returns an int
and a RequestB
that returns a String
. You could clearly do something like:
class Conversation<Q,R> {
R request (Q q, Class<R> rType) {
// Send the query (Q) and get a response R
}
}
class ConversationA extends Conversation<RequestA, Integer> {
}
class ConversationB extends Conversation<RequestB, String> {
}
A more fleshed-out version might look something like:
public class Test {
// Extend this to magically get a JSON-Like toString.
public static interface JSONObject {
public String asJSON();
}
class RequestA implements JSONObject {
@Override
public String asJSON() {
return "RequestA {}";
}
}
class RequestB implements JSONObject {
@Override
public String asJSON() {
return "RequestB {}";
}
}
static class Conversation<Q extends JSONObject, R> {
// Parser factory.
private static final JsonFactory factory = new JsonFactory();
// General query of the website. Takes an object of type Q and returns one of class R.
public R query(String urlBase, String op, Q q, Class<R> r) throws IOException {
// Prepare the post.
HttpPost postRequest = new HttpPost(urlBase + op);
// Get it all into a JSON string.
StringEntity input = new StringEntity(q.asJSON());
input.setContentType("application/json");
postRequest.setEntity(input);
// Post it and wait.
return requestResponse(postRequest, r);
}
private <R> R requestResponse(HttpRequestBase request, Class<R> r) throws IOException {
// Start a conversation.
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = httpclient.execute(request);
// Get the reply.
return readResponse(response, r);
}
private <R> R readResponse(CloseableHttpResponse response, Class<R> r) throws IOException {
// What was read.
R red = null;
try {
// What happened?
if (response.getStatusLine().getStatusCode() == 200) {
// Roll out the results
HttpEntity entity = response.getEntity();
if (entity != null) {
// Always make sure the content is closed.
try (InputStream content = entity.getContent()) {
red = parseAs(content, r);
}
}
} else {
// The finally below will clean up.
throw new IOException("HTTP Response: " + response.getStatusLine().getStatusCode());
}
} finally {
// Always close the response.
response.close();
}
return red;
}
private <R> R parseAs(InputStream content, Class<R> r) throws IOException {
JsonParser rsp;
// Roll it directly from the response stream.
rsp = factory.createJsonParser(content);
// Bring back the response.
return rsp.readValueAs(r);
}
}
static class ConversationA extends Conversation<RequestA, Integer> {
}
static class ConversationB extends Conversation<RequestB, String> {
}
public void test() throws IOException {
Integer a = new ConversationA().query("http://host/api", "JSON", new RequestA(), Integer.class);
String b = new ConversationB().query("http://host/api", "JSON", new RequestB(), String.class);
}
public static void main(String args[]) {
try {
new Test().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
This is derived from a real use of JSON
and Apache HttpClient
- however, It may not work as posted, as I have removed most of the error handling and retry mechanisms for simplicity. It is here primarily to demonstrate the use of the suggested mechanism.
Note that although there is no casting in this code (as required by the question) there is likely to be casting happening behind the scenes in rsp.readValueAs(r)
which you cannot get around with JSON
.
Each switch (or if/else if/else chain) based on types is a sign for a bad OO design.
As OldCurmudgeon stated: each request is bound to its response - a request and a response are a pair. So I would do exactly what you suggest in your text, but didn't implement in your code:
About the first problem a solution I came up with is with the use of polymorphism. So have a parent class Request and for every type of request have a subclass of Request, so have a RequestTypeA and RequestTypeB. All of the classes override the execute method. So the base classes looks like:
/**
* Abstract class Request forms the base class for all your requests.
* Note that the implementation of execute() is missing.
*/
interface Request {
public Response execute();
}
/**
* Response-Interface just to have a common base class.
*/
interface Response {
}
Note that I changed Request from a concrete class to an interface. The concrete implementation for A (with covariant return types I avoid the need for casting) looks like:
/**
* Concrete request of type A.
*/
class RequestTypeA implements Request {
/** all fields typically for request A. */
private int i;
/**
* ctor, initializes all request fields.
*/
public RequestTypeA(int i) {
this.i = i;
}
/**
* Provide the exact response type. A feature Java 5 introduced is covariant return types, which permits an overriding method to return a more specialized type than the overriden method.
*/
public ResponseTypeA execute()
{
// Your implementation here
// you have to return a ResponseTypeA
}
}
class ResponseTypeA implements Response {
int getResponse() {
// Your implementation here
}
}
And the concrete implementation for B:
/**
* Concrete request of type B.
*/
class RequestTypeB implements Request {
/** all fields typically for request B. */
private String s;
/**
* ctor, initializes all request fields.
*/
public RequestTypeB(String s) {
this.s = s;
}
/**
* Provide the exact response type. A feature Java 5 introduced is covariant return types, which permits an overriding method to return a more specialized type than the overriden method.
*/
public ResponseTypeB execute()
{
// Your implementation here
// you have to return a ResponseTypeB
}
}
class ResponseTypeB implements Response {
String getResponse() {
// Your implementation here
}
}
This design ensures that:
Usage example:
RequestTypeA reqA = new RequestTypeA(5);
ResponseType resA = regA.execute();
int result = resA.getResponse();
A solution with generics (presented by OldCurmudgeon) is also fine. Use a manual implementation of all request/response pairs and not generics when:
A toy implementation in Groovy (Java on steroids) querying the Internet Chuck Norris Database:
abstract class Request {
public abstract Response execute();
protected String fetch(String url) { new URL("http://api.icndb.com/jokes/$url").getText() }
}
interface Response {}
class RandomRequest extends Request {
public CommonResponse execute() {
new CommonResponse(result: fetch('random/'))
}
}
class SpecificRequest extends Request {
private int number;
public CommonResponse execute() {
new CommonResponse(result: fetch("$number"))
}
}
class CommonResponse implements Response {
private String result
String getJoke() {
def slurper = new groovy.json.JsonSlurper()
slurper.parseText(result).value.joke
}
}
println new RandomRequest().execute().joke
println new SpecificRequest(number: 21).execute().joke
Using generics, you could do something like this:
public class SomeClass {
private Object body;
@SuppressWarnings("unchecked")
public <T> T getBody(Class<T> type) {
if(type.isAssignableFrom(body.getClass())) {
return (T) body;
}
return null;
}
public void setBody(Object body) {
this.body = body;
}
}
It still involves casting to T
but at least you do it in the bowels of one method instead of constantly having to check for class type and cast return values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With