Why not use instanceof operator in OOP design?

Solution 1:

instanceof simply breaks the Open/Close principle. and/or Liskov substitution principle

If we are not enough abstract because of instanceof usage, each time a new subclass makes an entrance, the main code gathering the logic of the application might be updated. This is clearly not what we want, since it could potentially break the existing code and reduce its reusability.

Therefore, a good usage of polymorphism should be preferred over the basic use of conditional.

Solution 2:

There's a good blog post called When Polymorphism Fails which is about this kind of scenario. Basically, you're right that it should be up to the Man to decide what to do with each kind of Animal. Otherwise, the code becomes fragmented and you end up violating principles such as Single Responsibility and Law of Demeter.

It wouldn't make sense to have code such as e.g. the following:

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

In that example, we're putting Man's logic outside of the Man class.

The problem with instanceof is that if you have a large amount of Animals, you'll end up with a long if-else-if for every one of them. It's hard to maintain and prone to errors where e.g. a new type of Animal is added, but you forget to add it to the if-else-if chain. (The visitor pattern is partly a solution to the latter problem, because when you add a new type to the visitor class, all of the implementations stop compiling and you're forced to go update them all.)

However, we can still use polymorphism to make the code simpler and avoid instanceof.

For example, if we had a feeding routine such as:

if (animal instanceof Cat) {
    animal.eat(catFood);
} else if (animal instanceof Dog) {
    animal.eat(dogFood);
} else if (...) {
    ...
}

We could eliminate the if-else-if by having methods such as Animal.eat(Food) and Animal.getPreferredFood():

animal.eat(animal.getPreferredFood());

With methods such as Animal.isFood() and Animal.isPet(), the example in the question could be written without instanceof as:

if (animal.isFood()) {
    eatIt(animal);
} else if (animal.isPet()) {
    playWithIt(animal);
}

Solution 3:

instanceof is a type system escape hatch. It can be used to do really evil things, like make generics not really generic, or extend a class hierarchy with ad-hoc virtual methods that never appear in the visible interface of those classes. Both of these things are bad for long-term maintainability.

More often than not, if you find yourself wanting to use instanceof, it means that there is something wrong with your design. Breaking the type system should always be a last resort, not something to be taken lightly.

I do not think your particular example warrants using instanceof. The object-oriented way to do this is to use the 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)
}

NOTE: I am aware that Scala has case classes, but I wanted to provide a general object-oriented solution.