4

I have class structure where

  • public abstract class AbstractBuilding implements some non-relevant interfaces for this question.
  • public abstract class AbstractAnimalBuilding extends AbstractBuiling
    And small number of classes following this structure:
  • public class AnimalBuildingA extends AbstractAnimalBuilding
  • public class AnimalBuildingB extends AbstractAnimalBuilding
  • public class AnimalBuildingC extends AbstractAnimalBuilding
  • public class AnimalBuildingD extends AbstractAnimalBuilding

In a totally separate class I have the following method:

@FXML
private Button btnAnimalBuildingA;
@FXML
private Button btnAnimalBuildingB;
@FXML
private Button btnAnimalBuildingC;
@FXML
private Button btnAnimalBuildingD;

for (AbstractAnimalBuilding animalBuilding: animalBuildings){
    if (animalBuilding instanceof AnimalBuildingA) {
        changeButtonDisplay(btnAnimalBuildingA)
    } else if (animalBuilding instanceof AnimalBuildingB){
        changeButtonDisplay(btnAnimalBuildingB)
    } else if (animalBuilding instanceof AnimalBuildingC) {
        changeButtonDisplay(btnAnimalBuildingC)
    } else if (animalBuilding instanceof AnimalBuildingD){
        changeButtonDisplay(btnAnimalBuildingD)
        //Do something specific here
    }
}

private void changeButtonDisplay(Button buttonToChange){
    button.setVisible(true);
}

Where animalBuildings is a Set<AbstractAnimalBuilding> containing any combination of AnimalBuildingX's.

Assuming the structure at the top needs to be kept (eg, AnimalBuildingX HAS to extend AbstractAnimalBuilding), what would be a better approach than the multiple if-then-else statements in determining what kind of building animalBuilding is?

Would it feasible to simply create a new Interface as outlined in this question and have each AnimalBuildingX implement it while still extending AbstractAnimalBuilding or is there a way I can do it using the structure I currently have.

Dan
  • 448
  • 7
  • 18
  • 1
    Is the *thing* to be done specific or is the *call* specific? If you can make it generic then that's the obvious choice. And just because they are all extended from that class doesn't mean it wouldn't make more sense to have an additional interface to call upon. – ChiefTwoPencils Oct 23 '15 at 06:30
  • 1
    The `thing` to be done is creating setting a specific button (which is hard coded in FXML) to visible through a common method. I've edited the question rather than trying to explain it in a comment. – Dan Oct 23 '15 at 06:41

3 Answers3

4

This is difficult to answer in general without more context.

One possibility is to create an abstract method in AbstractBuilding and implement it differently in the subclasses.

Another possibility is to use the visitor pattern.

Henry
  • 42,982
  • 7
  • 68
  • 84
3

It depends on the action you want to take on behalf of the derived class type. If an action has to be taken which can be perfomed without the need, that the calling class knows the concrete implementation of AnimalBuilding the interface method is appropriate. This usually is the case if you can find a common method description which is implemented differently for each concrete class (e.g. getName()).

If you need to do specific actions dependent on the concrete class (e.g. AnimalBuildingA differs from AnimalBuldingB), you can implement the visitor pattern:

public abstract class AbstractAnimalBuilding {
    ...
    public abstract void accept(AnimalBuildingVisitor v);
}

public interface class AnimalBuildingVisitor<T> {

    public T visit(AnimalBuildingA a);
    public T visit(AnimalBuildingB b);
    ...
}

The implementation of the accept-method usually is the one liner

return v.visit(this);

Then you create an implementation of the Abstract visitor which does the work you want to perform in the loop. The loop then looks like this

ConcreteAnimalBuildingVisitor v;
for (AbstractAnimalBuilding animalBuilding: animalBuildings)
    animalBuilding.accept(v);

This way, the concrete class "identifies" itself to the concrete visior which then can perform the appropriate action.

flo
  • 9,713
  • 6
  • 25
  • 41
2

You can keep your current structure and achieve what you desire by using generics:

First we need to define a generic handler interface:

public interface AnimalBuildingHandler<T extends AbstractAnimalBuilding> {
    void handle(T type);
}

And then, in your own custom class, we can implement specific function for each types:

/* Here you can define all  */
public void handleAnimalBuildingA(AnimalBuildingA animalBuildingA) {
    /**
     * Implement your custom handling here
     */
    System.out.println("Handling AnimalBuildingA" + animalBuildingA);
}

public void handleAnimalBuildingB(AnimalBuildingB animalBuildingB) {
    /**
     * Implement your custom handling here
     */
    System.out.println("Handling AnimalBuildingA" + animalBuildingB);
}

And then, we can create a magic handler class that implements the above AnimalBuildingHandler interface by mapping handlers to types just like this:

private Map<Class<? extends AbstractAnimalBuilding>, AnimalBuildingHandler<? extends AbstractAnimalBuilding>> handlersMapping;

{ /* default instance initializer */
    handlersMapping = new HashMap<>();
    handlersMapping.put(AnimalBuildingA.class, new AnimalBuildingHandler<AnimalBuildingA>() {
        @Override
        public void handle(AnimalBuildingA type) {
            handleAnimalBuildingA(type);
        }
    });

    handlersMapping.put(AnimalBuildingB.class, new AnimalBuildingHandler<AnimalBuildingB>() {
        @Override
        public void handle(AnimalBuildingB type) {
            handleAnimalBuildingB(type);
        }
    });
}

@Override
public void handle(AbstractAnimalBuilding type) {
    AnimalBuildingHandler abh = handlersMapping.get(type.getClass());
    abh.handle(type);
}

And finally, the test method:

public <T extends AbstractAnimalBuilding> void test() {

    List<T> allAnimalBuildings = new ArrayList<>();

    allAnimalBuildings.add((T) new AnimalBuildingA());
    allAnimalBuildings.add((T) new AnimalBuildingB());

    for (AbstractAnimalBuilding aab : allAnimalBuildings) {
        handle(aab);
    }
}
Andrei Mărcuţ
  • 1,273
  • 17
  • 28
  • 1
    It's not exactly what I'd do but I like it (+1). With J8 you can actually take a slightly different direction and use the functional interface and so some variation of `map.get(foo).accept(bar);` or whatever suits your needs. – ChiefTwoPencils Oct 23 '15 at 07:55