Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphism, how to avoid type casting?

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:

  1. The execute method is going to be full of if, else if blocks.
  2. It could be that when a wrong response is returned, e.g. one where 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?

like image 732
insumity Avatar asked Oct 01 '14 22:10

insumity


People also ask

Is type casting polymorphism Java?

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();

What is type casting when is it necessary?

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.

Is type casting possible from boolean to char?

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.


3 Answers

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.

like image 131
OldCurmudgeon Avatar answered Oct 24 '22 09:10

OldCurmudgeon


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:

  • each Response is bound to its Request, because the request is the only way to get the response
  • you can access requests and responses through their common interface (or make an abstract class if you want to share functionality).
  • each Request and Respond can have it's specific input and output parameters (more than once)
  • you can access the parameters in a typesafe way

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:

  • each request / response has different parameters (and not only one)
  • you want to use plain data types instead of their boxed variants
  • the code for sending / retrieving is not so uniform that only the data type handling is different for the specializations.

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
like image 21
ChrLipp Avatar answered Oct 24 '22 09:10

ChrLipp


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.

like image 1
SergeyB Avatar answered Oct 24 '22 07:10

SergeyB