Emil
Emil

Reputation: 13799

How to avoid large if-statements and instanceof

Animal

public abstract class Animal {
 String name;

 public Animal(String name) {
  this.name = name;
 }

}

Lion

public class Lion extends Animal {

 public Lion(String name) {
  super(name);
  // TODO Auto-generated constructor stub
 }

 public void roar() {
  System.out.println("Roar");
 }
}

Deer

public class Deer extends Animal {

 public Deer(String name) {
  super(name);
 }

 public void runAway() {
  System.out.println("Running...");
 }

}

TestAnimals

public class TestAnimals {
 public static void main(String[] args) {
  Animal lion = new Lion("Geo");
  Animal deer1 = new Deer("D1");
  Animal deer2 = new Deer("D2");

  List<Animal> li = new ArrayList<Animal>();
  li.add(lion);
  li.add(deer1);
  li.add(deer2);
  for (Animal a : li) {
   if (a instanceof Lion) {
    Lion l = (Lion) a;
    l.roar();
   }
   if (a instanceof Deer) {
    Deer l = (Deer) a;
    l.runAway();
   }

  }
 }
}

Is there a better way to iterate through the list without having to cast ?In the above case it seem's ok but if you have many extensions of the base class then we'll need that many if block too.Is there a design pattern or principle to address this problem ?

Upvotes: 20

Views: 5498

Answers (9)

aioobe
aioobe

Reputation: 421290

An elegant way of avoiding instanceof without inventing some new artificial method in the base class (with a non-descriptive name such as performAction or doWhatYouAreSupposedToDo) is to use the visitor pattern. Here is an example:

Animal

import java.util.*;

abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public abstract void accept(AnimalVisitor av);  // <-- Open up for visitors.

}

Lion and Deer

class Lion extends Animal {
    public Lion(String name) {
        super(name);
    }
    public void roar() {
        System.out.println("Roar");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this);                            // <-- Accept and call visit.
    }
}


class Deer extends Animal {

    public Deer(String name) {
        super(name);
    }

    public void runAway() {
        System.out.println("Running...");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this);                            // <-- Accept and call visit.
    }

}

Visitor

interface AnimalVisitor {
    void visit(Lion l);
    void visit(Deer d);
}

class ActionVisitor implements AnimalVisitor {

    public void visit(Deer d) {
        d.runAway();
    }

    public void visit(Lion l) {
        l.roar();
    }
}

TestAnimals

public class TestAnimals {
    public static void main(String[] args) {
        Animal lion = new Lion("Geo");
        Animal deer1 = new Deer("D1");
        Animal deer2 = new Deer("D2");

        List<Animal> li = new ArrayList<Animal>();
        li.add(lion);
        li.add(deer1);
        li.add(deer2);
        for (Animal a : li)
            a.accept(new ActionVisitor());         // <-- Accept / visit.
    }
}

Upvotes: 35

Andy
Andy

Reputation: 8692

It turns out that instanceof is faster than the visitor pattern presented above; I think this should make us question, is the visitor pattern really more elegant than instanceof when it's doing the same thing more slowly with more lines of code?

Here's my test. I compared 3 methods: the visitor pattern above, instanceof, and an explicit type field in Animal.

OS: Windows 7 Enterprise SP1, 64-bit
Processor: Intel(R) Core(TM) i7 CPU 860 @ 2.80 GHz 2.93 GHz
RAM: 8.00 GB
JRE: 1.7.0_21-b11, 32-bit

import java.util.ArrayList;
import java.util.List;

public class AnimalTest1 {
    public static void main(String[] args) {
        Animal lion = new Lion("Geo");
        Animal deer1 = new Deer("D1");
        Animal deer2 = new Deer("D2");

        List<Animal> li = new ArrayList<Animal>();
        li.add(lion);
        li.add(deer1);
        li.add(deer2);

        int reps = 10000000;

        long start, elapsed;

        start = System.nanoTime();
        for (int i = 0; i < reps; i++) {
            for (Animal a : li)
                a.accept(new ActionVisitor()); // <-- Accept / visit.
        }
        elapsed = System.nanoTime() - start;

        System.out.println("Visitor took " + elapsed + " ns");

        start = System.nanoTime();
        for (int i = 0; i < reps; i++) {
            for (Animal a : li) {
                if (a instanceof Lion) {
                    ((Lion) a).roar();
                } else if (a instanceof Deer) {
                    ((Deer) a).runAway();
                }
            }
        }
        elapsed = System.nanoTime() - start;

        System.out.println("instanceof took " + elapsed + " ns");

        start = System.nanoTime();
        for (int i = 0; i < reps; i++) {
            for (Animal a : li) {
                switch (a.type) {
                case Animal.LION_TYPE:
                    ((Lion) a).roar();
                    break;
                case Animal.DEER_TYPE:
                    ((Deer) a).runAway();
                    break;
                }
            }
        }
        elapsed = System.nanoTime() - start;

        System.out.println("type constant took " + elapsed + " ns");
    }
}

abstract class Animal {
    public static final int LION_TYPE = 0;
    public static final int DEER_TYPE = 1;

    String name;
    public final int type;

    public Animal(String name, int type) {
        this.name = name;
        this.type = type;
    }

    public abstract void accept(AnimalVisitor av); // <-- Open up for visitors.
}

class Lion extends Animal {
    public Lion(String name) {
        super(name, LION_TYPE);
    }

    public void roar() {
        // System.out.println("Roar");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this); // <-- Accept and call visit.
    }
}

class Deer extends Animal {

    public Deer(String name) {
        super(name, DEER_TYPE);
    }

    public void runAway() {
        // System.out.println("Running...");
    }

    public void accept(AnimalVisitor av) {
        av.visit(this); // <-- Accept and call visit.
    }

}

interface AnimalVisitor {
    void visit(Lion l);

    void visit(Deer d);
}

class ActionVisitor implements AnimalVisitor {

    public void visit(Deer d) {
        d.runAway();
    }

    public void visit(Lion l) {
        l.roar();
    }
}

Test results:

Visitor took 920842192 ns
instanceof took 511837398 ns
type constant took 535296640 ns

This visitor pattern introduces 2 extra method calls that are unnecessary with instanceof. This is probably why it's slower.

Not that performance is the only consideration, but notice how 2 instanceofs are faster than even a 2-case switch statement. Plenty of people have worried about the performance of instanceof, but this should put the worry to rest.

As a Java Developer, I feel frustrated when people have a dogmatic attitude about avoiding the use of instanceof, because there have been several times in my work I wanted to clean up or write new clean code by using instanceof, but coworkers/superiors didn't approve of this approach , because they have more or less blindly accepted the idea that instanceof should never be used. I feel frustrated because this point is often driven home with toy examples that don't reflect real business concerns.

Whenever you pursue modular software design, there will always be times when type-dependent decisions need to be isolated from the types in question, so that the types have as few dependencies as possible.

This visitor pattern doesn't break modularity, but it's not a superior alternative to instanceof.

Upvotes: 2

Adeel Ansari
Adeel Ansari

Reputation: 39907

Animal

public abstract class Animal {
 String name;

 public Animal(String name) {
  this.name = name;
 }

 public abstract void exhibitNaturalBehaviour();

}

Lion

public class Lion extends Animal {

 public Lion(String name) {
  super(name);
 }

 public void exhibitNaturalBehaviour() {
  System.out.println("Roar");
 }
}

Deer

public class Deer extends Animal {

 public Deer(String name) {
  super(name);
 }

 public void exhibitNaturalBehaviour() {
  System.out.println("Running...");
 }

}

TestAnimals

public class TestAnimals {
 public static void main(String[] args) {

  Animal[] animalArr = {new Lion("Geo"), new Deer("D1"), new Deer("D2")};
  for (Animal a : animalArr) {
     a.exhibitNaturalBehaviour();    
  }

 }
}

Upvotes: 13

Peter Lawrey
Peter Lawrey

Reputation: 533870

The simplest approach is to have the super class implement a default behaviour.

public enum AnimalBehaviour { 
     Deer { public void runAway() { System.out.println("Running..."); } },
     Lion { public void roar() { System.out.println("Roar"); } }
     public void runAway() { } 
     public void roar() { }
 } 

 public class Animal {
     private final String name;
     private final AnimalBehaviour behaviour;
     public Animal(String name, AnimalBehaviour behaviour) {
         this.name = name;
         this.behaviour = behaviour;
     }
     public void runAway() { behaviour.runAway(); } 
     public void roar() { behaviour.roar(); }
  }

 public class TestAnimals { 
   public static void main(String... args) { 
     Animal[] animals = { 
       new Animal("Geo", AnimalBehaviour.Lion), 
       new Animal("Bambi", AnimalBehaviour.Deer), 
       new Animal("D2", AnimalBehaviour.Deer) 
     }; 

     for (Animal a : animals) {
       a.roar(); 
       a.runAway(); 
     } 
   }
 }

Upvotes: 1

Eric Grindt
Eric Grindt

Reputation: 31

Pattern matching support in the language eliminates the need for the ugly visitor pattern.

See this Scala code for example:

abstract class Animal(name: String)

class Lion(name: String) extends Animal(name) {
  def roar() {
    println("Roar!")
  }
}

class Deer(name: String) extends Animal(name) {
  def runAway() {
    println("Running!")
  }
}

object TestAnimals {
  def main(args: Array[String]) {
    val animals = List(new Lion("Geo"), new Deer("D1"), new Deer("D2"))
    for(animal <- animals) animal match {
      case l: Lion => l.roar()
      case d: Deer => d.runAway()
      case _       => ()
    }
  }
}

Upvotes: 3

Steven
Steven

Reputation: 3894

Consider adding an interface for the action (Roar, Run away, etc) which is set on the animal in the constructor. Then have an abstract method such as act() on the Animal class which gets called similar to what Adeel has.

This will let you swap in actions to act out via a field at any time.

Upvotes: 1

Colin Hebert
Colin Hebert

Reputation: 93197

Here you have a List of animals. Usually when you have a list of Objects, all these objects must be able to do the same thing without being casted.

So the best two solutions are :

  • Having a common method for the two concrete classes (so defined as abstract in Animal)
  • Separate Lion from Deer from the start, and have two different lists.

Upvotes: 2

Petar Minchev
Petar Minchev

Reputation: 47403

If your method is not polymorphic you can't do without the cast. To make it polymorphic, declare a method in the base class and override it in the descendant classes.

Upvotes: 2

Jigar Joshi
Jigar Joshi

Reputation: 240966

Yes provide a method called action() in abstract class , implement it in both of the child class, one will roar other will runaway

Upvotes: 5

Related Questions