I looked multiple places for some advice on how to solve this before asking this question, but I couldn't find something similar to this.
So I have the following scenario in Java Spring Integration
Let's say I have some super class type
class Foo {
private String error;
public Foo(error) {
this.error = error;
}
public boolean isError(){
return !(error.equals("SUCCESS"));
}
}
and then a few subclasses
class Bar extends Foo {
private String name;
public Bar(String name) {
this.name = name
super("SUCCESS");
}
public String getName(){
return name;
}
// more methods
}
And there are more classes that are subclasses of Foo
each with different methods. Now I have a Spring integration Gateway Facade that returns the supertype Foo, with a route that contacts different endpoints and returns a different subclass of Foo based on the request, or in some cases an error which is actually an instantiation of Foo containing the error string. The error comes from the Facades error handler.
public interface Facade {
Foo facadeMethod(Map<String, String> requestMap);
}
public class ErrorHandler {
// arguments not important hence "..."
public Foo handle(...) {
return new Foo("ERROR");
}
}
The way I am currently handling this is:
Foo response = facade.facadeMethod(requestMap);
if (response.isError()) {
// do something
}
Bar bar = (Bar)response;
String name = bar.name();
// something with bar methods
It is guaranteed that if isError
is false, the type I downcast to is actually of the subclass type. The route is selected based on a key/value pair in the requestMap
.
I could throw an exception instead of having an error code, but I still have the same issue of needing to downcast since the facade needs to return a more general type (super class). I don't see how to use generics to solve this, since the Facade method is not in the inheritance hierarchy of Foo/Bar and the other sub classes. I also thought about using a factory but I didn't see how to use that either.
This all feels messy, and I would like to avoid downcasting. Is there any way that is cleaner and better Java practice, or am I stuck with downcasting? Maybe I am missing something obvious here.
-
This sounds like what you really want is an Either type Because Java doesn't have one built in, I'm leaving this as a comment. Writing your own wouldn't be that difficult though.cbojar– cbojar2018年11月09日 14:58:01 +00:00Commented Nov 9, 2018 at 14:58
1 Answer 1
You could use the following mechanism: you have no if
s, no generics, no casts, no dangerous variables and a typesafe answer.
interface Response {
void handle(Feedback feedback);
}
interface Feedback {
default void onSuccess(Success success) {}
default void onError(Error error) {}
}
class Success implements Response {
String name;
Success(String name) { this.name = name; }
String name() { return name; }
public void handle(Feedback feedback) {
feedback.onSuccess(this);
}
}
class Error implements Response {
public void handle(Feedback feedback) {
feedback.onError(this);
}
}
Then, just use it like this:
Feedback feedback = new Feedback() {
public void onSuccess(Success success) {
String name = success.name();
// Do something with Success methods
}
public void onError(Error error) {
// Do something with Error methods
}
};
Response response = facade.facadeMethod(requestMap);
response.handle(feedback);
Notes:
- I renamed
Foo
toResponse
andBar
toSuccess
for the clarity of the answer. - I got rid of your error mechanism implementation because it was better handled this way. The "guarantee" you requested now comes from the distinction in two different feedback methods:
onSuccess
andonError
. - It can be extended by creating new
Response
subtypes and newon...
methods. You can of course call the sameon...
method for two different responses: it's the reponse's responsibility to call the feedback's appropriate method.
-
Exactly what I wanted! Also, what if
onSuccess
needs to return something? Like it constructs a new object, call itClientData
, out of the data it gets from theSuccess
object. How can this response be propagated up to return ofhandle
in a clean way? Basically, if the last block of code you wrote is in some method that wraps around the spring-integration functionality, and in the end returns theClientData
to the client.dylan7– dylan72018年11月15日 19:57:15 +00:00Commented Nov 15, 2018 at 19:57 -
and for another
on...
it returns something else as thaton...
was called from a different wrapper method that results in a different integration response.dylan7– dylan72018年11月15日 20:10:15 +00:00Commented Nov 15, 2018 at 20:10 -
@dylan7 For the
ClientData
, you can always keep working in theonSuccess
method. If you use a HTTP client, for instance, you get aHTTPResponse
somewhere or something similar. Then you can most certainly, in youronSuccess
callhttpResponse.write(clientData)
. Another possibility if you really want toreturn
is to returnOptional<ClientData>
instead ofvoid
. If you use some Json API, there is usually a possibility to get a response holder as parameter, mostly because asynchronicity is a thing now, and that's how you publish asynchronous responses.Olivier Grégoire– Olivier Grégoire2018年11月16日 10:23:22 +00:00Commented Nov 16, 2018 at 10:23 -
So going the
return
route, ifOptional<ClientData>
needed to be propagated up to thereturn
ofhandle
would we the needhandle
to have a genericreturn
type? Since not every class that implementsResponse
willreturn
Optional<ClientData>
in itshandle
. Since you mentioned asynchronicity, this is starting to feel like aFuture
-like pattern, which uses a generic.dylan7– dylan72018年11月16日 16:44:06 +00:00Commented Nov 16, 2018 at 16:44
Explore related questions
See similar questions with these tags.