[英]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;
}
}
上述解決方案有兩個問題:
execute
方法將充滿if
, else if
塊。someString
未初始化的響應,例如它與類型 A 的請求的響應混淆。 關於第一個問題,我想出的解決方案是使用多態性。 所以有一個父類Request
並且對於每種類型的請求都有一個Request
的子類,所以有一個RequestTypeA
和RequestTypeB
。 所有的類都覆蓋了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)
綁定在一起。
您有一個返回int
的RequestA
和一個返回String
的RequestB
。 您顯然可以執行以下操作:
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.