3

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 Nov 9 '18 at 14:58
1

You could use the following mechanism: you have no ifs, 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 to Response and Bar to Success 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 and onError.
  • It can be extended by creating new Response subtypes and new on... methods. You can of course call the same on... 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 it ClientData, out of the data it gets from the Success object. How can this response be propagated up to return of handle 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 the ClientData to the client. – dylan7 Nov 15 '18 at 19:57
  • and for another on... it returns something else as that on... was called from a different wrapper method that results in a different integration response. – dylan7 Nov 15 '18 at 20:10
  • @dylan7 For the ClientData, you can always keep working in the onSuccess method. If you use a HTTP client, for instance, you get a HTTPResponse somewhere or something similar. Then you can most certainly, in your onSuccess call httpResponse.write(clientData). Another possibility if you really want to return is to return Optional<ClientData> instead of void. 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 Nov 16 '18 at 10:23
  • So going the return route, if Optional<ClientData> needed to be propagated up to the return of handle would we the need handle to have a generic return type? Since not every class that implements Response will return Optional<ClientData> in its handle. Since you mentioned asynchronicity, this is starting to feel like a Future-like pattern, which uses a generic. – dylan7 Nov 16 '18 at 16:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.