Избегайте использования "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 списков: один для травоядных и один для хищных животных.