How to avoid large if-statements and instanceof

前端 未结 9 2333
盖世英雄少女心
盖世英雄少女心 2020-11-29 05:11

Animal

public abstract class Animal {
 String name;

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

}

Lion<

相关标签:
9条回答
  • 2020-11-29 06:13

    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.
        }
    }
    
    0 讨论(0)
  • 2020-11-29 06:13

    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.

    0 讨论(0)
  • 2020-11-29 06:16

    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();    
      }
    
     }
    }
    
    0 讨论(0)
提交回复
热议问题