簡體   English   中英

多態,如何避免類型轉換?

[英]Polymorphism, how to avoid type 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();
 }

為使上述代碼正確運行, 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 ...
        }
 }

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;
      }
 }

上述解決方案有兩個問題:

  1. execute方法將充滿if , else if塊。
  2. 可能是當返回錯誤響應時,例如someString未初始化的響應,例如它與類型 A 的請求的響應混淆。

關於第一個問題,我想出的解決方案是使用多態性。 所以有一個父類Request並且對於每種類型的請求都有一個Request的子類,所以有一個RequestTypeARequestTypeB 所有的類都覆蓋了execute方法。

關於 2. 問題我只有一個可能的想法來解決它:類似於Request根據Response創建Response的子類,並有類似的東西。

 interface Response {
 }

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

 class ResponseTypeB {
     ResponseTypeB(String s) { ... verify s is valid ... }
     String getStringResponse() { ... }
 }

現在我可以確定,如果響應類型為ResponseTypeB它將包含一個有效的字符串。 我可以編寫客戶端代碼如下:

String requestTypeB() {
    Request request = new Request(TypeB);
    ResponseTypeB response = (ResponseTypeB) request.execute();
    return response.getStringResponse();
 }

現在我不得不對execute的返回類型進行類型轉換。

我的主要問題/問題是:在上述情況下,有沒有辦法避免類型轉換? 或者,如果您知道上述問題的更好解決方案(設計模式?)?

試圖將請求與響應分開是徒勞的。 它們通過 API - R r = f(Q)綁定在一起。

您有一個返回intRequestA和一個返回StringRequestB 您顯然可以執行以下操作:

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

}

更充實的版本可能如下所示:

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

這是從JSON和 Apache HttpClient的實際使用中派生出來的 - 但是,它可能無法像發布的那樣工作,因為為了簡單起見,我已經刪除了大部分錯誤處理和重試機制。 這里主要是為了演示所建議機制的使用。

請注意,盡管此代碼中沒有強制轉換(根據問題的要求),但在rsp.readValueAs(r)可能會在幕后進行rsp.readValueAs(r) ,而JSON rsp.readValueAs(r)

每個基於類型的開關(或 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 {
}

請注意,我將 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
    }
}

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

這種設計確保:

  • 每個響應都綁定到它的請求,因為請求是獲得響應的唯一途徑
  • 您可以通過它們的公共接口訪問請求和響應(如果您想共享功能,則可以創建一個抽象類)。
  • 每個請求和響應都可以有它特定的輸入和輸出參數(不止一次)
  • 您可以以類型安全的方式訪問參數

用法示例:

    RequestTypeA reqA = new RequestTypeA(5);
    ResponseType resA = regA.execute();
    int result = resA.getResponse();

帶有泛型的解決方案(由 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

其他答案在泛型方面走在正確的軌道上,但由於需要額外的類和響應類型的冗余聲明,它們太復雜了。

它可以很簡單:

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

甚至

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

您不需要任何強制轉換,因此它不會引發ClassCastException而是編譯錯誤,就像沒有泛型一樣。

其他例子:

    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;

為什么我不能跳過變量的泛型類型聲明?

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

如果您沒有顯式聲明泛型類型,Java 會將其作為Object處理。 因此getResult()將返回一個對象。 因為 Java 是一種強類型語言,所以你不能在沒有強制轉換的情況下將一個 Object 放入一個 Integer 變量中。 沒有解決方法。


響應類型綁定到請求以避免在使用它時進行類型聲明。 如果一種請求類型可以返回不同的響應類型,則它可能封裝得不夠好,您應該將其拆分為兩種不同的請求類型或重構響應類型。

我假設您已經知道如何獲取 HTTP 響應,因此我跳過了那部分。

/**
 * 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);
    }

    ...
}

PS 如果您不喜歡子類AbstractRequest ,您可以將其設為非抽象並直接實例化它。 在這種情況下,您可以在 Java 7 及更高版本中使用菱形運算符:

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

使用泛型,你可以做這樣的事情:

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;
    }
}

它仍然涉及到T轉換,但至少你是在一種方法的內部完成的,而不是經常檢查類類型和轉換返回值。

也許更面向對象的方法是:

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; }
}

這樣你就可以使用以下內容:

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];
}

每個Type都有一個特定的響應內容類型。 這應該在代碼中表示,並且可以使用泛型來完成。

interface Type<T> {}

不同的類型可以定義為常量。

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

請求的實例化變化很小。

Request request = new Request(Type.A);

可以修改響應以使用Type訪問其內容。

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

這種方式鑄造是沒有必要的。

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

大多數 IDE 或編譯器都會報告Type和響應的內容類型之間的任何不匹配。


Response可以實現為一個泛型類。

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

Request可以使用多態來實現。

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");
    }
}

請求的實例化可以移動到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();
}

下面是產生的方法調用。

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

我使用 enum 來存儲所有可能的返回類型。

public enum Type {
INT, STRING
}

為請求和響應類定義子類。

Request 類的每個子類都覆蓋其 execute 方法並返回其對應的 Response 子類實例。

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;
}
}

最后在您的調用方法中使用它

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

}

}

像這樣怎么樣?

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:

我使用字符串作為響應,因為整數需要一個參數。

保持簡單:

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;
}
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM