Tim
Tim

Reputation: 2050

Java: If-else instanceof extended classes

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

Answers (6)

Luca Basso Ricci
Luca Basso Ricci

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

ajb
ajb

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

mike
mike

Reputation: 5055

APPROACH 1

Use the state pattern. It takes care of your problem and eliminates the ifs and elses.

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 ifs and elses. 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 Ys doStuff() method and just execute it.

Upvotes: 0

efan
efan

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

Cruncher
Cruncher

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

John B
John B

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

Related Questions