10

我正在努力避免在我的一些代码中使用 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()吗?

4

5 回答 5

12

访问者模式解决了您的问题。这是代码:

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...
于 2012-04-04T20:14:45.900 回答
5

在您的情况下,如果对象的使用者必须知道有关该对象的某些事情(例如它是肉),请在您的基类中包含一个属性isMeat()并让具体的子类覆盖基类方法的实现以返回适当的值。

将知识留在课堂本身,而不是课堂的消费者中。

于 2012-04-04T19:13:24.783 回答
4

一个简单的解决方案是当使用多个自定义类相互交互时,只需创建 isFood()、isAnimal()、isCarnivore() 等方法,这些方法根据它们所在的类返回一个布尔值。这不是最漂亮的,但它100% 的时间完成工作。

于 2012-04-04T19:14:11.677 回答
1

扩展我的评论,我会尝试使用泛型来帮助我:

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但是,它确实允许您更具体地了解您的子类接受的内容。

于 2012-04-04T19:24:48.943 回答
0

另一种解决方案是维护 2 个列表:一个用于草食动物,一个用于食肉动物。

于 2012-04-04T20:01:45.970 回答