Reputation: 2050
I have an abstract class X
and some classes who extend this class, call them A
, B
and C
.
In some other class Y
I have a few methodcalls that depend on the type of the class. The if-else statement looks like this:
public class Y implements InterfaceY {
public Y(){
}
public String doStuff (X x, Boolean flag) {
String s = "";
if (x instanceof A) {
doStuff((A) x));
} else if (x instanceof B) {
doStuff((B) x));
} else if (x instanceof C) {
doStuff((C) x, flag);
} else {
throw new Exeption();
}
return s;
private String doStuff(A a) {
return "";
}
private String doStuff(B b) {
return "";
}
private String doStuff(C c, Boolean flag) {
return "";
}
}
Note that all methods have the same name (doStuff()
) but depending on the class (and sometimes flag) call a different method implementation of that method. Of course this looks horrible and gets immensely complicated once the classed that are extended from X increase.
Is there any way that I can somehow create an intermediate Interface that (or something else) that takes care of the majority (or all) of the if-else statements?
Upvotes: 0
Views: 4922
Reputation: 18403
Separate a DoStuffOperation
, create the relative factory and use them.
public interface DoStuffOperation<T> {
String doStuff(T x);
}
public class ADoStuffImpl implements DoStuffOperation<A> {
public String doStuff(A x) {
return "doStuff<A>";
}
}
public class ADoStuffWithFlagImpl implements DoStuffOperation<A> {
public String doStuff(A x) {
return "doStuffWithFlag<A>";
}
}
public class DoStuffImplFactory {
public final static <T extends X> DoStuffOperation<X> getDoStuff(Class<T> xClass,boolean flag) {
DoStuffOperation<X> impl = null;
if(xClass.equals(A.class))
{
if(flag)
impl = (DoStuffOperation)new ADoStuffWithFlagImpl();
else
impl = (DoStuffOperation)new ADoStuffImpl();
}
}
return impl;
}
}
public class Y implements InterfaceY {
public String doStuff (X x, Boolean flag) {
return DoStuffImplFactory.getDoStuff(x.getClass(),flag).doStuff(x);
}
}
In this way you don't have to refactor call to Y.doStuff()
or X
and derived classes.
You can't remove at all some sort of instanceof
to decide which implementation of doStuff()
use unless X
classes implements a DoStuffCreator interface like:
interface DoStuffCreator {
DoStuffOperation getDoStuffOperation(boolean flag);
}
X
and A
are your classes. You can also construct using reflection or other automatic way (external property file and so on).
Upvotes: 0
Reputation: 31699
This can be a difficult problem. I think Cruncher's solution, add
doStuff
to X and override it in A
, B
, C
, is the simplest and
best solution when it's appropriate. However, it isn't always
appropriate, because of the Single responsibility
principle.
(I think that's the correct term. My apologies if I get some
terminology wrong, I'm not entirely up-to-date on all of the terms.)
The idea is that you shouldn't necessarily doStuff
to X
if it has
nothing to do with the purpose of X
. If X
and Y
are part of the
same "team", i.e. they've been both set up to serve the purpose of one
particular application, then it's probably OK.
But suppose you have an abstract Shape
class that has subclasses
Circle
, Square
, Undecagon
, RandomBlob
, etc. There will be
some methods that belong in the Shape
class that would be useful to
any application that uses the Shape
class. But now say you are
writing a game that uses some of those shapes, and you want a
polymorphic operation that determines what happens when the shape gets
eaten by a flying monkey. You wouldn't want to add an abstract
computeEatenByFlyingMonkey
method to your Shape
class, even if the
class were your own creation and not in someone else's library,
because that would be just too specific for a class that could be
generally used for other purposes than this one game.
I can think of a couple ways to approach this.
If it's not appropriate (or not possible) to add doStuff
to X
, but
if A
, B
, and C
are more closely connected to your application so
that adding doStuff
to them is appropriate, you can add another
class:
public abstract class XWithStuff extends X {
// repeat any constructors in X, making them all be just
// calls to super(...)
public abstract void doStuff (Boolean flag);
}
public class A extends XWithStuff {
@Override
public void doStuff (Boolean flag) { ... }
}
and so on for every other class. (XWithStuff
is just an example
name; in real life, a name that contains both "X" and some reference
to the application or purpose is probably better.) (P.S. I don't know
why you're using Boolean
instead of boolean
but I'm leaving it
that way in case there's a good reason.)
If it's also not appropriate or not possible to add doStuff
to A
,
B
, and C
, here's a possible solution:
public interface StuffInterface {
public void doStuff (Boolean flag);
}
public class AWithStuff extends A implements StuffInterface {
@Override
public void doStuff (Boolean flag) { ... }
}
and then in your program create objects of class AWithStuff
instead
of A
, etc. To call doStuff
on an X
:
void doStuff (X x, Boolean flag) {
if (x instanceof StuffInterface) {
((StuffInterface) x).doStuff (flag);
} else {
throw new IllegalArgumentException ();
}
}
If that's not an option and you have to deal directly with A
, B
,
etc., and you can't add doStuff
to those classes, then any solution
will be a bit hacky. If you don't want to use if
-then
-else
, you
could look into the visitor pattern, or you could conceivably create
a HashMap<Class<?>,Interf>
that would map A.class
, B.class
,
etc., to some interface object that calls the correct doStuff
. But
I haven't worked out the details. (Actually, the "visitor pattern" probably wouldn't be appropriate unless you have some sort of complex structure composed of objects of type X.)
Upvotes: 0
Reputation: 5055
APPROACH 1
Use the state pattern. It takes care of your problem and eliminates the if
s and else
s.
Here is the java example.
The state pattern delegates the methods calls to objects that implement the same interface but with different behaviour.
State pattern example:
public class StatePatternExample {
public static void main(String[] args) {
Girlfriend anna = new Girlfriend();
// OUTPUT
anna.kiss(); // *happy*
anna.greet(); // Hey, honey!
anna.provoke(); // :@
anna.greet(); // Leave me alone!
anna.kiss(); // ...
anna.greet(); // Hey, honey!
}
}
interface GirlfriendInteraction extends GirlfriendMood {
public void changeMood(GirlfriendMood mood);
}
class Girlfriend implements GirlfriendInteraction {
private GirlfriendMood mood = new Normal(this);
public void provoke() {
mood.provoke();
}
public void kiss() {
mood.kiss();
}
public void greet() {
mood.greet();
}
public void changeMood(GirlfriendMood mood) {
this.mood = mood;
}
}
interface GirlfriendMood {
public void provoke();
public void kiss();
public void greet();
}
class Angry implements GirlfriendMood {
private final GirlfriendInteraction context;
Angry(GirlfriendInteraction context) { // more parameters, flags, etc. possible
this.context = context;
}
public void provoke() {
System.out.println("I hate you!");
}
public void kiss() {
System.out.println("...");
context.changeMood(new Normal(context));
}
public void greet() {
System.out.println("Leave me alone!");
}
}
class Normal implements GirlfriendMood {
private final GirlfriendInteraction context;
Normal(GirlfriendInteraction context) {
this.context = context;
}
public void provoke() {
System.out.println(":@");
context.changeMood(new Angry(context));
}
public void kiss() {
System.out.println("*happy*");
}
public void greet() {
System.out.println("Hey, honey!");
}
}
As you can see, the class Girlfriend
has no if
s and else
s. It looks pretty clean.
The class Girlfriend
corresponds to your abstract class X
, the classes Normal
and Angry
correspond to A
, B
and C
.
Your class Y
then directly delegates to X without checking any cases.
APPROACH 2
Use the command pattern. You could then hand over a command object to Y
s doStuff()
method and just execute it.
Upvotes: 0
Reputation: 968
What about some double dispatch?
class X {
public String letYdoStuff(Y y, Boolean flag) {
return y.doStuff(this, flag);
}
public static void main(String [] args) {
//X x = new A();
X x = new B();
Y y = new Y();
y.doStuff(x, false);
}
public X getThis() {
return this;
}
}
class A extends X {
public String letYdoStuff(Y y, Boolean flag) {
return y.doStuff(this, flag);
}
}
class B extends X {
public String letYdoStuff(Y y, Boolean flag) {
return y.doStuff(this, flag);
}
}
class C extends X {
public String letYdoStuff(Y y, Boolean flag) {
return y.doStuff(this, flag);
}
}
class Y {
public Y(){
}
public String doStuff (X x, Boolean flag) {
String s = "";
return x.letYdoStuff(this, flag);
}
public String doStuff(A a, Boolean flag) {
System.out.println("in A");
return "";
}
public String doStuff(B b, Boolean flag) {
System.out.println("in B");
return "";
}
public String doStuff(C c, Boolean flag) {
System.out.println("in C");
return "";
}
}
Upvotes: 0
Reputation: 7812
First take these methods out of here, and put them in the A, B and C class respectively, implementing the X interface.
private String doStuff(A a) {
return "";
}
private String doStuff(B b) {
return "";
}
private String doStuff(C c, Boolean flag) {
return "";
}
Then:
if (x instanceof A) {
doStuff((A) x));
} else if (x instanceof B) {
doStuff((B) x));
} else if (x instanceof C) {
doStuff((C) x, flag);
can just be x.doStuff();
(you don't even have to pass the A, B, C because that will be this
inside the method. The flag you'll have to mess around with depending more specifically on your code. for example, the other 2 doStuff
methods could accept the flag as well, but just ignore it)
Upvotes: 1
Reputation: 32959
What about implementing a Handler
interface then map it by supported type:
public interface Handler<T extends X>{
Class<T> supportedClass;
void doStuff(T value, Object...args);
}
public class Y implements InterfaceY {
private Map<Class<?>, Handler<?>> handlers;
public Y(List<Handler<?>> handlers){
// populate map
}
public void process(X value){
handler.get(value.getClass).doStuff(X, ...);
// you would have to figure out how to determine when other values are needed
}
}
Upvotes: 0