简体   繁体   中英

Alternatives for instanceof when processing concrete types in Java

Let's say I have the following class hierarchy:

public interface Result {
}
public class Failure implements Result {
    private String msg;
    public Failure(String msg) {
        this.msg = msg;
    }
    @Override public String toString() { return msg; }
}
public class Success implements Result {
    private int time, count;
    public Success(int time, int count) { this.time = time; this.count = count; }
    public int getTime() { return time; }
    public int getCount() { return count; }
    @Override public String toString() { return time + ", " + count; }
}

which basically defines an interface Result with two concrete classes: Success and Failure.

Then, I have a calculate method that returns a result and based on the result I should do something, for example:

Result result = calculate();

I have several options to figure it out the concrete class. The most obvious one is to use instance of:

if (result instanceof Success) {
    int time = result.getTime();
    int count = result.getCount();
    // Do something with them
} else {
    throw new RuntimeException(result.toString());
}

In programming languages like Scala that support pattern matching, I could use a match like

case Success =>
case Failure =>

but since Java doesn't support it, I'm not sure what's the best to do. I can for example extend the Result interface as follows:

public interface Result {
    public boolean isSuccess();
    public boolean isFailure();
    public Success asSuccess();
    public Failure asFailure();
}

and then write the client code as:

if (result.isSuccess()) {
   Success success = result.asSuccess();
   int time = success.getTime();
   int count = success.getCount();
   // Do something with them
} else {
   throw new RuntimeException(result.toString());
}

but in this case the interface is bound to concrete classes which is not good either.

What is the best way to represent such cases? I really don't want to put the processing code in the Result hierarchy as it really up to the client to get the data and do whatever they want, so polymorphic dispatch solutions may not be the best. Result hierarchy is at best a data type encapsulation, not operation.

IMO, this is an inappropriate usage of "Marker Interface" pattern. You do want to use some functionalities defined by the interface, then don't make it as a Marker. Add methods in. To make it generic, you may want to know the type/category of the Result like Success, Error, Fatal, Warning, etc... you can consider to make an Enum, enum ResultType and in your interface, except for the functional methods ( getTime, getCount... ) add a getType() or, just use an abstract class Result and declare a protected Type type .

To decide concrete type and do corresponding operation, you can consider to do with case statements on those enum types. But you don't have to cast them to concrete type, because all functionalities you want are defined in the interface/abstract class Result .

sorry for the lack of code example. Not convenient to code right now.

If there are not only two subtypes, then the visitor pattern would be suitable. Consider:

interface Result {
    // resut of the implementation
    public <T> T accept(Visitor<T> visitor);
}

class Success implements Result {
    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }
}

class Failure implements Result {
    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }
}

Then create a visitor class:

interface Visitor<T> {
    T visit(Failure failure);
    T visit(Success success);
}

Then where you would use instanceof :

Result result = ...;
// this could be something else, with a different template parameter.
String visitorResult = result.accept(new Visitor<String>() {
    @Override
    public String visit(Failure failure) {
        return "Failure visited";
    }

    @Override
    public String visit(Success success) {
        return "Success visited";
    }
});

This is only my idea of implementation, with generics you can add more customizable features. If you want to read more about this: http://en.wikipedia.org/wiki/Visitor_pattern

This can be simplified to

public interface Result {
  public boolean isSuccess();
  public Integer getTime();
  public Integer getCount();
}

You'll have to implement count and time in failure (return a zero/null or throw exception), and then :

if (result.isSuccess()) {
  int time = result.getTime();
  int count = result.getCount();
  // Do something with them
} 
else {
  throw new RuntimeException(result.toString());
}

You don't need toString on interface, but you may want to as it explicity tell others dev's to worry about if there is another result (BigSuccess, MssvieFail etc) implemented in future.

A boolean on result, signifying success seems simple and intuitive.

One thing to note here is that if result.isSuccess() returns true in your code, you don't need to have an asSuccess() method to cast it to Success , you can simple use type casting (Success) result without worrying it will throw a runtime exception.

I don't think instanceof is that much of a big deal, but if you still want to have a workaround, I was thinking of maybe something like that:

public interface Result {
    public String getType();
}

public class Success implements Result {
    public static final String TYPE = "Success";

    //..... Some class relevant code here

    public String getType() {
        return TYPE;
    }
}

public class Failure implements Result {
    public static final String TYPE = "Failure";

    //..... Some class relevant code here

    public String getType() {
        return TYPE;
    }
}

Then, if you are using JDK version of 7 or greater ( which supports switch statements with Strings ), you can try the following:

switch(result.getType()) {
    case Success.TYPE:
         //Success code here
    case Failure.TYPE:
         //Failure code here
}

I have not tested this, but I think it would satisfy your needs.

I hope I helped you!

Additional fields in the interface will not realy limit the "usability" of that interface. Normaly interfaces are designed to get used by concrete classes.

In your case I would prefer a universal Result class that handles "Success" and "Failure".

There is one more option: the visitor pattern:

public interface ResultVisitor {
    void handleSuccess(Success result);
    void handleFailure(Failure result);
}

public interface Result {
    void accept(ResultVisitor visitor);
}
public class Failure implements Result {
    private String msg;
    public Failure(String msg) {
        this.msg = msg;
    }
    @Override public String toString() { return msg; }
    @Override void accept(ResultVisitor visitor) {visitor.visit(this);}
}
public class Success implements Result {
    private int time, count;
    public Success(int time, int count) { this.time = time; this.count = count; }
    public int getTime() { return time; }
    public int getCount() { return count; }
    @Override public String toString() { return time + ", " + count; }
    @Override void accept(ResultVisitor visitor) {visitor.visit(this);}
}

Then, somewhere in your code:

result.accept(new ResultVisitor() {
    void handleSuccess(Success success) {
        // do something with success
    }
    void handleFailure(Failure failure) {
        // do something with failure
    }
});

but in this case the interface is bound to concrete classes which is not good either.

If this is your concern and you expect to have additional implementations of success and failure then just make Success and Failure interfaces.

All objects have a toString() so you might not need Failure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM