Reputation: 7345
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.
Upvotes: 6
Views: 1024
Reputation: 13807
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
Upvotes: 1
Reputation: 7976
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.
Upvotes: 0
Reputation: 195059
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.
Upvotes: 2
Reputation: 32831
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
}
});
Upvotes: 0
Reputation: 47290
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.
Upvotes: 0
Reputation: 189
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".
Upvotes: 0
Reputation: 26281
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!
Upvotes: 0