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

动物

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 li = new ArrayList(); 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块。是否有设计模式或原则解决这个问题?

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

动物

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

动物

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

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

事实certificate,instanceof比上面提到的访问者模式更快; 我认为这应该让我们有疑问,访问者模式真的比instanceof更优雅,当它用更多代码行更慢地做同样的事情时?

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

操作系统:Windows 7 Enterprise SP1,64位
处理器:Intel(R)Core(TM)i7 CPU 860 @ 2.80 GHz 2.93 GHz
RAM: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 li = new ArrayList(); 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的优秀替代品。

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

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

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

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

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

请参阅此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 _ => () } } } 

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

这将允许您随时交换操作以通过字段执行操作。

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

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