2012-04-04 7 views
10

Я изо всех сил пытаюсь использовать 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 
    } 
} 

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

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

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

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

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

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

+0

Может быть, вы должны смотреть в общих методов? Таким образом, вы могли бы указать, что у Животного есть способ есть (T extends Food), а Herbivore будет набираться как Plant? Что-то в этом роде. –

+1

Возможный дубликат [Используется ли это «экземпляр» Оператор считается плохой дизайн?] (Http://stackoverflow.com/questions/8841577/is-this-use-of-the-instanceof-operator-considered-bad- дизайн) –

+0

Идеальный кандидат для шаблона посетителя – bluesman

ответ

5

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

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

+0

Итак, случай для установки isMeat() в базовом классе, который лучше, чем использование 'instanceof', заключается в том, что в последнем случае знание о (возможной) реализации переносится в класс клиента? Я предполагаю, что это снизит вес этой передачи знаний клиенту с загромождением базового объекта методами 'isXXX'. Я вижу, что разные случаи, возможно, опрокидывают масштаб в любом направлении. – HolySamosa

4

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

+0

Для этого требуется отлив: – chris

+0

См. Комментарий выше. Вы, ребята, дали один и тот же ответ одновременно! – HolySamosa

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. Но, это позволяет вам быть более конкретным с тем, что ваши подклассы принимают.

+0

См. Http://stackoverflow.com/questions/9806785/best-practice-for-enforcing-type-safety-in-polymorphic-inheritance-hierarchies/9807170#9807170 Этот вопрос упоминался выше, но он обсуждает некоторые потенциальных ловушек такого использования дженериков. Для записи, тем не менее, я раньше рассматривал именно такое решение. – HolySamosa

0

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

+0

Это одна вещь, о которой я думал, но мне кажется, что мне все равно нужно будет использовать instanceof(), чтобы проверить тип пищи, чтобы решить, в какой список передать питание. – HolySamosa

+0

Ну, вам также понадобятся каналы для приема пищи: один обеспечивает только растения, только обеспечивающие мясо. – Puce

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... 
+1

Реализованный код путем извлечения интерфейса AnimalVistor из класса Food – chris

+2

Хотя ботаник во мне любит видеть код для шаблона посетителя в действии (браво!), Он был разработан для добавления новых посетителей без нарушения классов посещений, а не для исключения экземпляра (который вот о чем вопрос). Имущественные решения (isMeat() и т. Д.) Менее сложны и [смиренны] (http://www.cs.utexas.edu/~EWD/transcriptions/EWD03xx/EWD340.html). – Fuhrmanator

Смежные вопросы