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:
上述解决方案存在两个问题:
- The
execute
method is going to be full ofif
,else if
blocks. - execute方法将充满if, else if块。
- 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. - 可能是当返回错误的响应时,例如某个字符串没有初始化,例如,它与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.
关于第一个问题,我提出的解决方案是使用多态性。有一个父类请求,对于每种类型的请求都有一个请求的子类,所以有一个RequestTypeA和RequestTypeB。所有的类都覆盖execute方法。
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.
的2。问题是,关于如何解决它,我只有一个可能的想法:类似于请求创建基于响应的响应子类,并具有类似的东西。
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:
现在我可以确定如果响应类型为ResponseTypeB它将包含一个有效的字符串。我可以写客户代码如下:
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
.
现在我必须键入cast执行的返回类型。
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?
我的主要问题是:在上述情况下是否有办法避免类型铸造?或者如果您意识到针对上述问题的更好的解决方案(设计模式?)?
9 个解决方案
#1
7
Trying to separate the request from the response is futile. They are bound together by the API - R r = f(Q)
.
试图将请求与响应分开是徒劳的。它们通过API - R = f(Q)结合在一起。
You have a RequestA
that returns an int
and a RequestB
that returns a String
. You could clearly do something like:
您有一个返回int的请求,和一个返回字符串的请求。很明显,你可以这样做:
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.
这是从JSON和Apache HttpClient的实际使用中派生出来的——但是,它可能不能像发布时那样工作,因为为了简单起见,我已经删除了大多数错误处理和重试机制。这里主要是演示所建议的机制的使用。
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
.
请注意,尽管这段代码中没有强制类型转换(如问题所要求的),但是在rsp.readValueAs(r)中,很可能在幕后发生强制类型转换,这是您无法使用JSON处理的。
#2
4
Each switch (or if/else if/else chain) based on types is a sign for a bad OO design.
基于类型的每个开关(或者if/else if/else链)都是糟糕的OO设计的标志。
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:
正如OldCurmudgeon所说:每个请求都绑定到它的响应—一个请求和一个响应是一对。所以我会按照你的建议去做,但是没有在你的代码中实现:
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:
关于第一个问题,我提出的解决方案是使用多态性。有一个父类请求,对于每种类型的请求都有一个请求的子类,所以有一个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 {
}
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:
注意,我将请求从具体类更改为接口。A的具体实现(使用协变返回类型,我避免了强制转换)如下:
/**
* Concrete request of type A.
*/
class RequestTypeA {
/** 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:
B的具体实施:
/**
* Concrete request of type B.
*/
class RequestTypeB {
/** 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:
使用泛型(由OldCurmudgeon提出)的解决方案也不错。使用手工实现所有请求/响应对,而不是泛型:
- 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:
在Groovy (Java on类固醇)中查询Internet Chuck Norris数据库的一个玩具实现:
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
#3
1
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.
它仍然涉及到对T的强制转换,但至少您可以在一个方法的内部进行,而不必经常检查类类型并强制转换返回值。
#4
1
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 ClassCastException
s but compile errors, as it would without generics.
您不需要任何转换,因此它不会引发classcastexception,而是编译错误,因为它没有泛型。
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.
如果没有显式声明泛型类型,Java将把它作为对象处理。因此getResult()将返回一个对象。因为Java是一种强类型语言,所以不允许在不强制转换的情况下将对象放入整型变量中。这是没有变通办法的。
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.
我假设您已经知道如何获得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);
}
...
}
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,可以使它非抽象并直接实例化。在这种情况下,您可以使用Java 7及以上的diamond操作符:
AbstractRequest<String> request = new AbstractRequest<>();
#5
0
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];
}
#6
0
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.
大多数ide或编译器都会报告响应的类型和内容类型之间的任何不匹配。
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);
}
#7
-1
I have used enum to store all possible return types.
我使用enum存储所有可能的返回类型。
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.
请求类的每个子类重写它的execute方法并返回相应响应的子类实例。
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());
}
}
#8
-1
How about like this ?
这样怎么样?
package com.example.*.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.
我使用字符串作为响应,因为integer需要一个参数。
#9
-1
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;
}
}
#1
7
Trying to separate the request from the response is futile. They are bound together by the API - R r = f(Q)
.
试图将请求与响应分开是徒劳的。它们通过API - R = f(Q)结合在一起。
You have a RequestA
that returns an int
and a RequestB
that returns a String
. You could clearly do something like:
您有一个返回int的请求,和一个返回字符串的请求。很明显,你可以这样做:
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.
这是从JSON和Apache HttpClient的实际使用中派生出来的——但是,它可能不能像发布时那样工作,因为为了简单起见,我已经删除了大多数错误处理和重试机制。这里主要是演示所建议的机制的使用。
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
.
请注意,尽管这段代码中没有强制类型转换(如问题所要求的),但是在rsp.readValueAs(r)中,很可能在幕后发生强制类型转换,这是您无法使用JSON处理的。
#2
4
Each switch (or if/else if/else chain) based on types is a sign for a bad OO design.
基于类型的每个开关(或者if/else if/else链)都是糟糕的OO设计的标志。
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:
正如OldCurmudgeon所说:每个请求都绑定到它的响应—一个请求和一个响应是一对。所以我会按照你的建议去做,但是没有在你的代码中实现:
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:
关于第一个问题,我提出的解决方案是使用多态性。有一个父类请求,对于每种类型的请求都有一个请求的子类,所以有一个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 {
}
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:
注意,我将请求从具体类更改为接口。A的具体实现(使用协变返回类型,我避免了强制转换)如下:
/**
* Concrete request of type A.
*/
class RequestTypeA {
/** 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:
B的具体实施:
/**
* Concrete request of type B.
*/
class RequestTypeB {
/** 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:
使用泛型(由OldCurmudgeon提出)的解决方案也不错。使用手工实现所有请求/响应对,而不是泛型:
- 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:
在Groovy (Java on类固醇)中查询Internet Chuck Norris数据库的一个玩具实现:
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
#3
1
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.
它仍然涉及到对T的强制转换,但至少您可以在一个方法的内部进行,而不必经常检查类类型并强制转换返回值。
#4
1
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 ClassCastException
s but compile errors, as it would without generics.
您不需要任何转换,因此它不会引发classcastexception,而是编译错误,因为它没有泛型。
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.
如果没有显式声明泛型类型,Java将把它作为对象处理。因此getResult()将返回一个对象。因为Java是一种强类型语言,所以不允许在不强制转换的情况下将对象放入整型变量中。这是没有变通办法的。
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.
我假设您已经知道如何获得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);
}
...
}
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,可以使它非抽象并直接实例化。在这种情况下,您可以使用Java 7及以上的diamond操作符:
AbstractRequest<String> request = new AbstractRequest<>();
#5
0
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];
}
#6
0
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.
大多数ide或编译器都会报告响应的类型和内容类型之间的任何不匹配。
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);
}
#7
-1
I have used enum to store all possible return types.
我使用enum存储所有可能的返回类型。
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.
请求类的每个子类重写它的execute方法并返回相应响应的子类实例。
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());
}
}
#8
-1
How about like this ?
这样怎么样?
package com.example.*.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.
我使用字符串作为响应,因为integer需要一个参数。
#9
-1
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;
}
}