Избегайте использования "instanceof"

Я борюсь с тем, как я мог бы избежать использования instanceof() в некоторых моих кодах. Этот надуманный пример несколько отражает проблему.

Class Meat extends Food;

Class Plant extends Food;

Class Animal;

Class Herbivore extends Animal
{
    void eat( Plant food);
}

Class Carnivore extends Animal
{
    void eat( Meat food);
}

Class Omnivore extends Animal
{
    void eat(Food food);
}

Class Zoo
{
    List<Animals> animals;

    void receiveFood( Food food)
    {
        // only feed Plants to Herbivores and Meat to Carnivores
        // feed either to Omnivores
    }
}

Травоядные животные интересуются только растениями, плотоядными животными только в мясе и омниверсе. Когда зоопарк получает Пищу, имеет смысл попытаться накормить пищу для животных, которые едят этот тип пищи.

Я думал о нескольких решениях, но все, кажется, зависит от использования instanceof() где-то, и мои различные рефакторинги просто перемещают его.

(1) Я мог реализовать eat( Food food) в Animal, и каждый подкласс мог выбрать игнорировать пищу, которую она не ест, но это неэффективно и потребует, чтобы каждый подкласс Animal использовал instanceof() для проверки типа пищи.

(2) Я мог бы хранить три коллекции животных в Зоопарке в зависимости от типа пищи, которую они едят, но все равно придется использовать instanceof(), чтобы проверить тип пищи, чтобы увидеть, какую коллекцию нужно кормить. По крайней мере, это было бы более эффективно, так как я не буду кормить животных животными, которые не будут есть.

Я думал о некоторых других подходах, но опять же, они просто передают instanceof() доллар.

Любые предложения? Или это (2, по крайней мере) было бы приемлемым использованием instanceof()?

Ответ 1

Шаблон посетителя решает вашу проблему. Здесь код:

public abstract class Animal {
  public abstract void accept(AnimalVisitor visitor);
}

public interface AnimalVisitor {
  public void visit(Omnivore omnivore);
  public void visit(Herbivore herbivore);
  public void visit(Carnivore carnivore);
}

public class Carnivore extends Animal {
  @Override
  public void accept(AnimalVisitor visitor) {
    visitor.visit(this);
  }

  public void eat(Meat meat) {
    System.out.println("Carnivore eating Meat...");
  }
}

public class Herbivore extends Animal {
  @Override
  public void accept(AnimalVisitor visitor) {
    visitor.visit(this);
  }

  public void eat(Plant plant) {
    System.out.println("Herbivore eating Plant...");
  }
}

public class Omnivore extends Animal {
  @Override
  public void accept(AnimalVisitor visitor) {
    visitor.visit(this);
  }

  public void eat(Food food) {
    System.out.println("Omnivore eating " + food.getClass().getSimpleName() + "...");
  }
}

public abstract class Food implements AnimalVisitor {
  public void visit(Omnivore omnivore) {
    omnivore.eat(this);
  }
}

public class Meat extends Food {
  @Override
  public void visit(Carnivore carnivore) {
    carnivore.eat(this);
  }

   @Override
  public void visit(Herbivore herbivore) {
    // do nothing
  }
}

public class Plant extends Food {
   @Override
  public void visit(Carnivore carnivore) {
    // do nothing
  }

   @Override
  public void visit(Herbivore herbivore) {
    herbivore.eat(this);
  }
}

public class Zoo {
  private List<Animal> animals = new ArrayList<Animal>();

  public void addAnimal(Animal animal) {
    animals.add(animal);
  }

  public void receiveFood(Food food) {
    for (Animal animal : animals) {
      animal.accept(food);
    }
  }

  public static void main(String[] args) {
    Zoo zoo = new Zoo();
    zoo.addAnimal(new Herbivore());
    zoo.addAnimal(new Carnivore());
    zoo.addAnimal(new Omnivore());

    zoo.receiveFood(new Plant());
    zoo.receiveFood(new Meat());
  }
}

Выполнение демонстрационных отпечатков Zoo

Herbivore eating Plant...
Omnivore eating Plant...
Carnivore eating Meat...
Omnivore eating Meat...

Ответ 2

В вашем случае, если потребитель объекта должен знать определенные вещи об этом объекте (например, это мясо), включить свойство в ваш базовый класс isMeat() и иметь конкретные подклассы, переопределить реализацию метода базового класса, чтобы верните соответствующее значение.

Оставьте это знание в самом классе, а не в потребителях класса.

Ответ 3

Простым решением является использование нескольких пользовательских классов, взаимодействующих друг с другом, просто создайте методы isFood(), isAnimal(), isCarnivore() и т.д., которые возвращают логическое значение в зависимости от того, в каком классе они находятся. самый красивый, но он выполняет работу в 100% случаев.

Ответ 4

Развернув мой комментарий, я попытаюсь использовать дженерики, чтобы помочь мне здесь:

interface Animal<T extends Food> {
    void eat(T food);
}

class Herbivore extends Animal<Plant> {
    void eat(Plant food) { foo(); }
}

class Carnivore extends Animal<Meat> {
    void eat(Meat food) { bar(); }
}

Обратите внимание, что это все еще не решает проблему итерации через список Food и Animal и отправляет только соответствующее питание каждому животному - я не вижу способа сделать это без явного instanceof проверки стиля. Но, это позволяет вам быть более конкретным с тем, что ваши подклассы принимают.

Ответ 5

Другим решением является сохранение 2 списков: один для травоядных и один для хищных животных.