如何避免使用大型if语句和instanceof

时间:2010-10-14 06:59:56

标签: java casting iteration

动物

public abstract class Animal {
 String name;

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

}

狮子

public class Lion extends Animal {

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

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

鹿

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();
   }

  }
 }
}

有没有更好的方法来迭代列表而不必进行转换?在上面的例子中它似乎没问题,但是如果你有很多基类的扩展,那么我们将需要那么多if块。是否有设计解决这个问题的模式或原则?

9 个答案:

答案 0 :(得分:31)

避免instanceof而不在基类中创建一些新的人工方法(使用非描述性名称,例如performActiondoWhatYouAreSupposedToDo)的优雅方法是使用visitor pattern 。这是一个例子:

<强>动物

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.

}

狮子鹿

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.
    }

}

<强>访问者

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.
    }
}

答案 1 :(得分:12)

<强>动物

public abstract class Animal {
 String name;

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

 public abstract void exhibitNaturalBehaviour();

}

<强>狮子

public class Lion extends Animal {

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

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

<强>鹿

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();    
  }

 }
}

答案 2 :(得分:5)

是在抽象类中提供一个名为action()的方法,在两个子类中实现它,一个会咆哮其他会失控

答案 3 :(得分:2)

如果你的方法不是多态的,你就离不开演员表。要使其具有多态性,请在基类中声明一个方法,并在子类中重写它。

答案 4 :(得分:2)

这里有List个动物。通常当你有一个对象列表时,所有这些对象必须能够在不进行转换的情况下执行相同的操作。

所以最好的两个解决方案是:

  • 拥有两个具体类的通用方法(在abstract中定义为Animal
  • 从一开始就将LionDeer分开,并有两个不同的列表。

答案 5 :(得分:2)

语言中的模式匹配支持消除了对丑陋访客模式的需求。

请参阅此Scala代码,例如:

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 _       => ()
    }
  }
}

答案 6 :(得分:2)

事实证明,instanceof比上面提到的访问者模式更快;我认为这应该让我们有疑问,访问者模式是否比使用更多代码行更慢地执行相同操作的实例更优雅?

这是我的测试。我比较了3种方法:上面的访问者模式,instanceof和Animal中的显式类型字段。

操作系统:Windows 7 Enterprise SP1,64位
处理器:Intel(R)Core(TM)i7 CPU 860 @ 2.80 GHz 2.93 GHz
内存:8.00 GB
JRE:1.7.0_21-b11,32位

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();
    }
}

测试结果:

访客花了920842192 ns
instanceof取511837398 ns
类型常量需要535296640 ns

此访问者模式引入了2个额外的方法调用,这些调用对于instanceof是不必要的。这可能是为什么它变慢了。

并不是性能是唯一的考虑因素,但请注意2个instanceofs比2个case switch语句更快。很多人都担心例子的表现,但这应该让人担心。

作为一名Java开发人员,当人们对避免使用instanceof采取教条态度时,我感到很沮丧,因为在我的工作中我曾经多次使用instanceof清理或编写新的清洁代码,但同事/上级不赞成这种方法,因为他们或多或少地盲目地接受了永远不应该使用instanceof的想法。我感到很沮丧,因为这一点往往带有玩具示例,并不能反映出真正的商业问题。

无论何时进行模块化软件设计,总会有时候需要将与类型相关的决策与相关类型隔离开来,以便类型具有尽可能少的依赖关系。

此访问者模式不会破坏模块化,但它不是instanceof的优秀替代方案。

答案 7 :(得分:1)

考虑为构造函数中的动物设置的动作(咆哮,逃跑等)添加界面。然后在Animal类上有一个抽象方法,比如act(),它被调用类似于Adeel所拥有的。

这样您就可以随时通过字段交换操作。

答案 8 :(得分:1)

最简单的方法是让超类实现默认行为。

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(); 
     } 
   }
 }