反复说过,除了equals()方法之外,不应该使用instanceof运算符,否则它是一个糟糕的OOP设计。
有人写道,这是一个繁重的操作,但似乎至少java处理得很好(甚至比Object.toString()比较更有效。)
有人可以解释一下,或者直接告诉我一些解释为什么这是一个糟糕设计的文章?
考虑一下:
Class Man{
doThingsWithAnimals(List<Animal> animals){
for(Animal animal : animals){
if(animal instanceOf Fish){
eatIt(animal);
}
else if(animal instanceof Dog){
playWithIt(animal);
}
}
}
...
}
决定如何处理动物,取决于男人。男人的欲望也可能偶尔改变,决定吃狗,玩鱼,而动物不会改变。
如果您认为instanceof运算符不是正确的OOP设计,请告诉您如何在没有instanceof的情况下执行此操作,以及为什么?
感谢。
答案 0 :(得分:10)
instanceof
只会破坏Open/Close principle.和/或Liskov substitution principle
如果由于instanceof
用法而导致抽象不够,每次新的子类进入时,收集应用程序逻辑的主代码都可能会更新。
这显然不是我们想要的,因为它可能会破坏现有代码并降低其可重用性。
因此,优先使用多态性应优先于条件的基本用法。
答案 1 :(得分:7)
有一篇名为When Polymorphism Fails的好博客文章就是关于这种情况。基本上,你应该由Man
决定如何处理每种Animal
。否则,代码会变得支离破碎,最终会违反Single Responsibility和Law of Demeter等原则。
拥有诸如此类的代码是没有意义的。以下内容:
abstract class Animal {
abstract void interactWith(Man man);
}
class Fish extends Animal {
@Override
void interactWith(Man man) {
man.eat(this);
}
}
class Dog extends Animal {
@Override
void interactWith(Man man) {
man.playWith(this);
}
}
在该示例中,我们将Man
的逻辑置于Man
类之外。
instanceof
的问题在于,如果您有大量的Animal
,那么每个人都会得到一个很长的if-else-if
。在以下情况下很难维护并容易出错。添加了一种新类型的Animal
,但您忘记将其添加到if-else-if
链中。 (The visitor pattern部分是后一个问题的解决方案,因为当您向访问者类添加新类型时,所有实现都会停止编译,并且您不得不全部更新它们。)
但是,我们仍然可以使用多态来使代码更简单并避免使用instanceof
。
例如,如果我们有一个喂养程序,例如:
if (animal instanceof Cat) {
animal.eat(catFood);
} else if (animal instanceof Dog) {
animal.eat(dogFood);
} else if (...) {
...
}
我们可以通过if-else-if
和Animal.eat(Food)
等方法消除Animal.getPreferredFood()
:
animal.eat(animal.getPreferredFood());
使用Animal.isFood()
和Animal.isPet()
等方法,问题中的示例可以在没有instanceof
的情况下编写:
if (animal.isFood()) {
eatIt(animal);
} else if (animal.isPet()) {
playWithIt(animal);
}
答案 2 :(得分:0)
instanceof
是一个类型系统逃生舱口。它可以用来做真正邪恶的事情,比如使泛型不是通用的,或者使用从未出现在这些类的可见界面中的特殊虚拟方法来扩展类层次结构。这两件事都不利于长期可维护性。
通常情况下,如果您发现自己想要使用instanceof
,则表示您的设计存在问题。打破类型系统应该永远是最后的手段,而不是一件轻松的事情。
我认为您的特定示例不保证使用instanceof
。面向对象的方法是使用visitor pattern:
abstract class Animal {
def accept(v: AnimalVisitor)
}
trait Edible extends Animal {
def taste : String
def accept(v: AnimalVisitor) = v.visit(this)
}
trait Pet extends Animal {
def growl : String
def accept(v: AnimalVisitor) = v.visit(this)
}
abstract class AnimalVisitor {
def visit(e: Edible)
def visit(p: Pet)
}
class EatOrPlayVisitor {
def visit(e: Edible) = println("it tastes " + e.taste)
def visit(p: Pet) = println("it says: " + p.growl)
}
class Chicken extends Animal with Edible {
def taste = "plain"
}
class Lobster extends Animal with Edible {
def taste = "exotic"
}
class Cat extends Animal with Pet {
def growl = "meow"
}
class Dog extends Animal with Pet {
def growl = "woof"
}
object Main extends App {
val v = new EatOrPlayVisitor()
val as = List(new Chicken(), new Lobster(), new Cat(), new Dog())
for (a <- as) a.accept(v)
}
注意:我知道Scala有案例类,但我想提供一个面向对象的通用解决方案。
答案 3 :(得分:0)
使用的实例是一种不好的做法,因为在OOP中无需检查类是什么, 如果该方法兼容,则应该可以使用此类参数来调用它,否则设计会被破坏,存在缺陷, 但是它的存在方式与C和C ++中的goto相同, 我认为有时使用的实例集成错误的代码可能会更容易,但是如果您编写自己的正确代码,请避免使用它 所以基本上,这与编程风格有关,好的和坏的是什么, 何时何地 在某些情况下,使用不良样式,因为有时代码质量是次要的,也许 有时,目标是使代码不容易被他人理解,这就是这样做的方式