insumity
insumity

Reputation: 5479

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?

Upvotes: 10

Views: 7432

Answers (9)

ChrLipp
ChrLipp

Reputation: 15678

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

Upvotes: 5

Christian Strempfer
Christian Strempfer

Reputation: 7383

The other answers were on the right track with generics, but they are too complicated by requiring additional classes and redundant declarations of response types.

It could be as easy as:

    Response<Integer> a = new RequestA().execute();
    int resultA = a.getResult();

or even

    String resultB = new RequestB().execute().getResult();

You won't need any casting and therefore it won't raise ClassCastExceptions but compile errors, as it would without generics.

Other examples:

    AbstractRequest<Integer> requestC = new RequestC(); 
    Integer resultC = requestC.execute().getResult();

    // The only use case where you need casting, is when you create 
    // a response type hierarchy.
    AbstractRequest<? extends MyBaseClass> requestD = new RequestE();
    MyBaseClass resultD = requestD.execute().getResult();
    MyConcreteClass resultD2 = (MyConcreteClass) resultD;

Why can't I skip the generic type declaration on variables?

AbstractRequest request = new RequestA(); 
Integer resultC = request.execute().getResult(); // compile error

If you don't explicitly declare the generic type, Java will handle it as Object. Therefore getResult() will return an Object. Because Java is a strongly typed language, you are not allowed to put an Object into a Integer variable without casting. There is no workaround for this.


The response type is bound to the request to avoid type declaration when using it. If one request type could return different response types, it's probably not encapsulated good enough and you should split it into two different request types or refactor the response type.

I assume you already know how to get the HTTP response, therefore I skipped that part.

/**
 * Response is a generic wrapper, which could contain any value.
 */
class Response<RETURN_TYPE> {
    private final RETURN_TYPE result;

    public Response(RETURN_TYPE result) {
        this.result = result;
    }

    public RETURN_TYPE getResult() {
        return result;
    }

    // Could contain additional meta data, like status code or warnings.
}

/**
 * AbstractRequest does the main work. Subclasses of AbstractRequest just
 * provide request parameters.
 */
abstract class AbstractRequest<RETURN_TYPE> {
    private final Class<RETURN_TYPE> returnType;

    /**
     * Return type has to be set explicitly, because the JSON parser needs
     * to know what class it should instantiate and type erasure prevents
     * accessing the generic type at runtime.
     */
    protected AbstractRequest(Class<RETURN_TYPE> returnType) {
        this.returnType = returnType;
    }

    /**
     * Request-dependent parameters must be set in sub classes.
     */
    protected abstract String getRequestUrl();

    public Response<RETURN_TYPE> execute() throws IOException {
        // I'll skip the details. You already know how to get here.
        InputStream response = ... ;

        // In real code you should reuse JsonFactory .
        JsonParser parser = new JsonFactory().createJsonParser(response);

        // Wrap it into a Response.
        return new Response<RETURN_TYPE>(parser.readValueAs(this.returnType));
    }
}

// Examples:

class RequestA extends AbstractRequest<Integer> {
    public RequestA() {
        super(Integer.class);
    }

    protected String getRequestUrl() {
        return "http://example.org/a";
    }
}

static class RequestB extends AbstractRequest<String> {
    public RequestB() {
        super(String.class);
    }

    ...
}

P.S. If you don't like to subclass AbstractRequest, you could make it non-abstract and instantiate it directly. In that case you could use the diamond operator with Java 7 and above:

    AbstractRequest<String> request = new AbstractRequest<>();

Upvotes: 1

CKuck
CKuck

Reputation: 712

Each Type has a specific content-type of the response. This should be represented in the code and can be done using generics.

interface Type<T> {}

The different types can be defined as constants.

interface Type<T> {
    Type<Integer> A = new Type<Integer>(){};
    Type<String>  B = new Type<String>(){};
}

The instantiation of the requests changes only minimally.

Request request = new Request(Type.A);

The response can be modified to use the Type to access its content.

interface Response {
    <T> T getContent(Type<T> type);
}

This way casting isn't necessary.

int requestTypeA() {
    Request request = new Request(Type.A);
    Response response = request.execute();
    return response.getContent(Type.A);
}

String requestTypeB() {
    Request request = new Request(Type.B);
    Response response = request.execute();
    return response.getContent(Type.B);
}

Any mismatch between Type and the content-type of the response will be reported by most IDEs or the compiler.


The Response can be implemented as a generic class.

class GenericResponse<C> implements Response {
    private final Type<C> type;
    private final C content;

    public GenericResponse(Type<C> type, C content) {
        this.type = type;
        this.content = content;
    }

    @Override
    public <T> T getContent(Type<T> type) {
        if (this.type == type)
            return (T) content;
        else
            throw new IllegalArgumentException();
    }
}

The Request can be implemented using polymorphism.

interface Request {
    Response execute();
}

class RequestTypeA implements Request {
    @Override
    public Response execute() {
        // do stuff
        return new GenericResponse<Integer>(Type.A, 123);
    }
}

class RequestTypeB implements Request {
    @Override
    public Response execute() {
        // do stuff
        return new GenericResponse<String>(Type.B, "done");
    }
}

The instantiation of the requests can be moved to Type.

interface Type<T> {
    Type<Integer> A = new Type<Integer>(){
        @Override
        public Request createRequest() {
            return new RequestTypeA();
        }
    };

    Type<String>  B = new Type<String>(){
        @Override
        public Request createRequest() {
            return new RequestTypeB();
        }
    };

    Request createRequest();
}

Here are the resulting method calls.

int requestTypeA() {
    Request request = Type.A.createRequest();
    Response response = request.execute();
    return response.getContent(Type.A);
}

String requestTypeB() {
    Request request = Type.B.createRequest();
    Response response = request.execute();
    return response.getContent(Type.B);
}

Upvotes: 0

SergeyB
SergeyB

Reputation: 9868

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.

Upvotes: 1

user2504380
user2504380

Reputation: 545

keeping it simple:

interface Request<RETURNVALUE>{Response<RETURNVALUE> execute();}

interface Response<RETURNVALUE>{RETURNVALUE getValue();}

//IMPL
class Client{

String requestTypeA(){
    Request<String> q = new RequestA();
    return q.execute().getValue();
}
}

class RequestA implements Request<String>{

@Override
public Response<String> execute() {
    return new ResponseA();
}

}

class ResponseA implements Response<String>{

@Override
public String getValue() {
    return null;
}
}

Upvotes: -1

Ravindra HV
Ravindra HV

Reputation: 2608

How about like this ?

package com.example.stackoverflow.oop;

public class Executor {

    public static void main(String[] args) throws Exception  {
        String req = "helloworld";
        String res = execute(req, String.class);
        System.out.println( "Res:" + res );
    }

    public static <T,R> R execute(T req, Class<R> res) throws Exception {
        System.out.println(req.toString());
        Object object = res.newInstance();
        return res.cast(object);
    }

}
-------------------------------------------------------------------------
helloworld
Res:

I used string as response since integer expects an argument.

Upvotes: -1

faheem farhan
faheem farhan

Reputation: 411

I have used enum to store all possible return types.

public enum Type {
INT, STRING
}

Define subclasses for Request and Response classes.

Each subclass of Request class overrides its execute method and returns its corresponding Response's subclass instance.

public class RequestINT extends Request {
public RequestINT(){
    super(Type.INT);
}
@Override
public Response execute() {
    return new ResponseINT();
}
}


public class ResponseINT extends Response {
@Override
public Type getResponse() {
    return Type.INT;
}
}

Finally use that in your calling method

public class TestExec {

public static void main(String[] args) {

    Request request1 = new RequestINT();
    Response response1 = request1.execute();
    System.out.println(response1.getResponse());

    Request request2 = new RequestSTRING();
    Response response2 = request2.execute();
    System.out.println(response2.getResponse());

}

}

Upvotes: -1

OldCurmudgeon
OldCurmudgeon

Reputation: 65879

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.

Upvotes: 7

Muli Yulzary
Muli Yulzary

Reputation: 2569

Maybe a more OO approach would be:

public interface Protocol{
 public enum Type{ SAMPLE_TYPE } //define types of protocol for ex: message, file transfer, etc...
 Type getType();
 Object[] getParams();
 Protocol execute();
}

public MyProtocol implements Protocol{
 private Type type;
 private Object[] params;

 public MyProtocol(Type t, Object... params){
  this.type = t;
  this.params = params;
 }

 public Protocol execute(){
  switch(this.type){
   case SAMPLE_TYPE:{
    //Your implementation here
    break;
  }
 }

 public Type getType(){ return Type; }
 public Object[] getParams(){ return params; }
}

That way you can use the following:

int requestTypeA() {
 int someNeededValueForExecution = 1337;
 String someNeededStringForExecution = "This is an example";
 Protocol request = new MyProtocol(Protocol.Type.SAMPLE_TYPE, someNeededValueForExecution, someNeededStringForExecution);
 Protocol response = request.execute();
 // You can have multiple parameters too, parse them or return them to caller
 return (int)response.getParams()[0];
}

Upvotes: 0

Related Questions