多态性,如何避免类型转换?

ins*_*ity 8 java oop polymorphism casting

我很抱歉这个长期的问题,但请耐心等待,我尽量让我的问题变得可以理解.如果你认为它可以更简洁随意编辑它.

我有一个客户端 - 服务器系统,客户端向服务器发送不同类型的请求,并根据请求获取响应.

客户端系统中的代码是:

 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();
 }
Run Code Online (Sandbox Code Playgroud)

为了使上面的代码正确运行,Request该类是:

 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 ...
        }
 }
Run Code Online (Sandbox Code Playgroud)

并且Response是这样的:

 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;
      }
 }
Run Code Online (Sandbox Code Playgroud)

上述解决方案有两个问题:

  1. 这个execute方法将充满if,else if块.
  2. 可能是当返回错误的响应时,例如,someString未初始化的响应,例如,它与A类请求的响应混淆.

关于第一个问题,我提出的解决方案是使用多态.所以有一个父类,Request并且对于每种类型的请求都有一个子类Request,所以有一个RequestTypeARequestTypeB.所有类都覆盖了该execute方法.

关于2.问题我只有一个可能的想法如何解决它:类似于Request创建Response基于响应的子类并具有类似的东西.

 interface Response {
 }

 class ResponseTypeA {
     ResponseTypeA(int i) { ... }
     int getIntResponse() { ... }
 }

 class ResponseTypeB {
     ResponseTypeB(String s) { ... verify s is valid ... }
     String getStringResponse() { ... }
 }
Run Code Online (Sandbox Code Playgroud)

现在我可以肯定,如果类型的响应ResponseTypeB它将包含一个有效的字符串.我可以编写客户端代码如下:

String requestTypeB() {
    Request request = new Request(TypeB);
    ResponseTypeB response = (ResponseTypeB) request.execute();
    return response.getStringResponse();
 }
Run Code Online (Sandbox Code Playgroud)

现在我不得不输入强制转换类型execute.

我的主要问题是:在上述情况下,有没有办法避免类型转换?或者,如果您了解上述问题的更好解决方案(设计模式?)?

Old*_*eon 7

试图将请求与响应分开是徒劳的.它们由API绑定在一起 - R r = f(Q).

你有一个RequestA返回an int和a RequestB返回一个String.你可以清楚地做一些事情:

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> {

}
Run Code Online (Sandbox Code Playgroud)

更加丰富的版本可能看起来像:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是源于JSON和Apache 的实际使用HttpClient- 但是,它可能无法发布,因为我已经删除了大多数错误处理和重试机制以简化.这里主要是为了证明建议机制的使用.

需要注意的是虽然在此代码中没有铸件(如要求的问题)有可能被铸造发生在幕后rsp.readValueAs(r),你不能得到解决JSON.

  • @foobar`Conversation <RequestA,Integer> conv = new ConversationB();`由于不匹配的泛型参数已经非法. (2认同)

Chr*_*ipp 5

每个基于类型的开关(或 if/else if/else 链)都是糟糕的 OO 设计标志

正如 OldCurmudgeon 所说:每个请求都绑定到它的响应——一个请求和一个响应是一对。所以我会完全按照你在文本中的建议去做,但没有在你的代码中实现:

关于第一个问题,我想出的解决方案是使用多态性。所以有一个父类Request,对于每种类型的请求都有一个Request的子类,所以有一个RequestTypeA和RequestTypeB。所有的类都覆盖了 execute 方法。 所以基类看起来像:

/**
 * 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 {
}
Run Code Online (Sandbox Code Playgroud)

请注意,我将 Request 从具体类更改为接口。A 的具体实现(使用协变返回类型我避免了强制转换)看起来像:

/**
 * 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

这种设计确保:

  • 每个响应都绑定到它的请求,因为请求是获得响应的唯一途径
  • 您可以通过它们的公共接口访问请求和响应(如果您想共享功能,则可以创建一个抽象类)。
  • 每个请求和响应都可以有它特定的输入和输出参数(不止一次)
  • 您可以以类型安全的方式访问参数

用法示例:

    RequestTypeA reqA = new RequestTypeA(5);
    ResponseType resA = regA.execute();
    int result = resA.getResponse();
Run Code Online (Sandbox Code Playgroud)

带有泛型的解决方案(由 OldCurmudgeon 提出)也很好。在以下情况下使用所有请求/响应对的手动实现而不是泛型:

  • 每个请求/响应都有不同的参数(不仅仅是一个)
  • 您想使用纯数据类型而不是它们的盒装变体
  • 发送/检索的代码不是那么统一,以至于专业化的数据类型处理是不同的。

查询Internet Chuck Norris 数据库的Groovy(类固醇上的 Java)中的玩具实现:

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
Run Code Online (Sandbox Code Playgroud)