Сомнения по поводу использования полиморфизма, а также о том, как полиморфизм связан с кастингом? - PullRequest
6 голосов
/ 04 июня 2011

Я даю уроки по основам языка программирования Java студентам, изучающим этот предмет в колледже.

Сегодня одна из них действительно запутала меня в ее вопросе, поэтому я попросил ее дать мне всего один день, чтобы подумать о проблеме, и я дам ей максимально точный ответ.

Она сказала мне, что учитель очень разозлился, когда она использовала ключевое слово instanceof в своем экзамене.

Также она сказала, что учитель сказал, что нет способа доказать, как работает полиморфизм, если она использует это слово.

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

Итак, вот пример, который я сделал:

public interface Animal
{
    public void talk();
}

class Dog implements Animal {        
    public void talk() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal
{
    public void talk() {
         System.out.println("Meow!");
    }    

    public void climbToATree() {
          System.out.println("Hop, the cat just cimbed to the tree");
    }
}

class Hippopotamus implements Animal {
    public void talk() {
        System.out.println("Roar!");
    }    
}

public class Main {
    public static void main(String[] args) {
        //APPROACH 1
        makeItTalk(new Cat());
        makeItTalk(new Dog());
        makeItTalk(new Hippopotamus());

       //APPROACH 2
        makeItClimbToATree(new Cat());
        makeItClimbToATree(new Hippopotamus());
    }

    public static void makeItTalk(Animal animal) {
        animal.talk();
    }

   public static void makeItClimbToATree(Animal animal) {
       if(animal instanceof Cat) {
            ((Cat)animal).climbToATree();                  
       }
       else {
           System.err.println("That animal cannot climb to a tree");
        }
    }
}

Мои выводы следующие:

  • Первый подход (ПОДХОД 1) - это простая демонстрация того, как программировать на интерфейс, а не реализация. Я думаю, что полиморфизм отчетливо виден в параметрах метода makeItTalk(Animal animal), а также в способе вызова метода с использованием объекта животного. (Эта часть в порядке)

  • Вторая часть меня смущает. Она использовала instanceof в какой-то момент своего экзамена (я не знаю, как выглядел их экзамен), и это было неправильно принято, потому что учитель сказал, что вы не доказываете полиморфизм.

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

Как вы можете видеть, только кошки могут карабкаться на деревья, и было бы не логично заставить Бегемота или Собаку подняться на дерево. Я думаю, что это может быть примером того, когда использовать instanceof

  • А как же полиморфизм в подходе 2?

  • Сколько случаев использования полиморфизма вы видите там (только подход 2)?

  • Как вы думаете, в этой строке есть какой-то тип полиморфизма?

    ((Cat)animal).climbToATree();

Я думаю, что так и есть, потому что для достижения Кастинга такого типа объекты должны иметь отношение IS-A, в некотором роде это полиморфизм.

  • Как вы думаете, это правильно?

  • Если да, как бы вы объяснили своими словами, что кастинг основан на полиморфизме?

Ответы [ 8 ]

5 голосов
/ 04 июня 2011

Причина, по которой метод instanceof считается плохим, проста.Кошки - не единственные Animal, которые могут забраться на дерево.

Что произойдет, если в будущем вам понадобится добавить класс Коала.Тогда ваш простой if становится не таким простым or.Что происходит, когда вы добавляете другой класс?и еще один.И еще один.Это главная причина, почему instanceof считается плохим.Потому что он связывает реализацию с конкретным классом, вместо того, чтобы открывать его для вызывающего, чтобы определить, что делать.

Просто реализуйте метод makeItClimbToATree() для выдачи CantClimbTreesException, если вызывается на животном, которое может 'т поднятьсяТаким образом, у вас есть лучшее из обоих миров.Простота реализации и простота расширения.

ИМХО, instanceof имеет только 1 действительно правильное применение: в тестовом случае для проверки возвращенного экземпляра из метода соответствует ожидаемый тип возвращаемого значения (в безопасном от типа)языки).

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

Другой способ взглянуть на это так: Полиморфизм позволяет вамудалить почти все условные выражения из вашего кода.Единственные условия, от которых невозможно избавиться (по крайней мере, от всех), - в методах создания объекта (например, на фабрике, где он должен выбирать класс на основе аргумента времени выполнения).Почти любой другой условный может быть заменен полиморфизмом.Следовательно, все, что делает условное исполнение, по определению является антиполиморфным.Это не значит, что это плохо (есть огромная разница между Хорошим и Хорошим достаточно ), но в академической дискуссии это не полиморфно ...

Никогда не забывайте правило 60/60.60% вашего общего времени разработки будет потрачено на поддержку написанного вами кода, а 60% этого времени будет потрачено на добавление новых функций.Упростите ведение, и ваша жизнь тоже станет проще.Вот почему instanceof это плохо.Это облегчает первоначальный дизайн, но усложняет долгосрочное обслуживание (которое в любом случае дороже) ...

4 голосов
/ 04 июня 2011

В приведенном выше примере, нет необходимости звонить

makeItClimbToATree (new Hippopotamus ());

Этого можно легко избежать, если makeItClimbToATree не будет ожидать животное, но что-то более конкретное, которое действительно способно залезть на дерево. Необходимость разрешать животным и, следовательно, использовать instanceof, не видна. Если вы управляете животными в Списке животных, это будет более очевидно.

Несмотря на то, что объяснение ircmaxells начинается великолепно, при представлении Koala и других TreeClimbers он не видит второго расширения, скрывающегося в морском анемоне: различные возможности животных, таких как seaAnemoneHider, winterSleeping, blueEyed, bugEating и так далее, и так далее. В результате вы получите логическое значение вместо логического, постоянно перекомпилирующее базовый класс, а также нарушающее расширяющиеся классы клиентов, которые снова потребуют перекомпиляцию, и не сможете представить свои собственные возможности подобным образом.

Клиенту A потребуется клиент B для объявления исключения NotBugEatingException, чтобы привести ваше поведение в базовый класс.

Представление ваших собственных интерфейсов в сочетании с instanceof - намного более чистый подход и более гибкий. Клиент A может определить, что труба divingLikeAPenguin и клиент B, оба не знают друг друга, не влияют на класс животных и не вызывают бесполезную перекомпиляцию.

import java.util.*;

interface Animal {
    public void talk ();
}

interface TreeClimbing {
    public void climbToATree ();
}

class Dog implements Animal {
    public void talk () { System.out.println("Woof!"); }
}

class Cat implements Animal, TreeClimbing {
    public void talk () { System.out.println("Meow!"); }    
    public void climbToATree () { System.out.println ("on top!"); }
}

public class TreeCriterion {

    public static void main(String[] args) {
        List <Animal> animals = new ArrayList <Animal> ();
        animals.add (new Cat ());
        animals.add (new Dog ());

        discuss (animals);
        upTheTree (animals);
    }

    public static void discuss (List <Animal> animals) {
        for (Animal a : animals)
            a.talk ();
    }

    public static void upTheTree (List <Animal> animals) {
        for (Animal a : animals) {
            if (a instanceof TreeClimbing)
                ((TreeClimbing) a).climbToATree ();
        }
    }
}

Нам не нужно третье животное, достаточно собаки и кошки. Я сделал их по умолчанию видимыми, а не публичными, чтобы весь пример поместился в один файл.

2 голосов
/ 04 июня 2011

Как вы думаете, в этой строке есть какой-то тип полиморфизма?

((Cat)animal).climbToATree();

Нет. Особенно, поскольку Cat является листовым классом в примере.

Я думаю, что так и есть, потому что для достижения Кастинга такого типа объекты должны иметь отношение IS-A, в некотором роде это полиморфизм.

Полиморфизм требует отношений IS-A, но не наоборот.

Полиморфизм - это когда вы отправляете (потенциально) различные методы, основанные на абстрактном интерфейсе. Если у вас нет такой диспетчеризации, тогда она не использует полиморфизм. В вашем примере, используя instanceof для приведения к классу без подклассов, вы устраняете необходимость в диспетчеризации.

(Конечно, существует несколько способов «сделать полиморфизм» в Java. Вы можете реализовать его с помощью интерфейсов, абстрактных классов или конкретных классов с подклассами ... или гипотетических подклассов, которые могут быть записаны в будущее. Интерфейсы (и диспетчеризация на основе интерфейса), как правило, являются наилучшим способом, поскольку они обеспечивают четкое отделение API от идентичности класса.)

И на отдельном примечании, использование instanceof как это , как правило, признак плохого дизайна и / или плохого моделирования. В частности, он жестко дополняет предположение, что только кошки могут лазить, что тривиально фальсифицируется, если мы включаем других животных в модель / программу. Если это произойдет, ваш код сломается.

0 голосов
/ 06 декабря 2012

Я удивлен, что никто не написал ничего о Поздней привязке.Полиморфизм в Java = Позднее связывание.Вызываемый метод будет присоединен к объекту, когда мы наконец узнаем его тип.В вашем примере:

 if(animal instanceof Cat) {
     ((Cat)animal).climbToATree();                  
 }

Вы вызываете climbToATree() для объекта Cat, поэтому компилятор принимает его.Во время выполнения нет необходимости проверять тип вызывающего объекта, поскольку climbToATree() принадлежит только Cat.И поэтому в этих строках кода нет полиморфизма.

Что касается приведения, связанного с полиморфизмом, то это не так.Приведение просто ограничивает поля, которые совместно используются в обоих объектах, если приведение допустимо.Вы можете сделать это:

class A {
    int getInt() {}
}

class B extends A {
    int getInt() {}
}

// in main
A a = new B();
A b = (A)a;
b.getInt(); // This would still call class B's getInt();

Само приведение не добавило значения, getInt () была привязана во время выполнения к типу времени выполнения a, который был классом B.

0 голосов
/ 04 июня 2011

Оператор instanceof не имеет ничего общего с полиморфизмом. Он просто используется, чтобы увидеть, является ли объект экземпляром определенного класса. Вы видите, что этот оператор часто используется в методе equals(), потому что метод принимает обобщенный Object в качестве параметра:

public class Cat implements Animal{
  @Override
  public boolean equals(Object obj){
    if (obj == null || !obj instanceof Cat){
      //obj is null or not a "Cat", so can't be equal
      return false;
    }
    if (this == obj){
      //it's the same instance so it must be equal
      return true;
    }
    Cat catObj = (Cat)obj; //cast to "Cat"
    return this.getName().equals(catObj.getName()); //compare the two objects
  }
}

Если класс не реализует метод, он должен вызвать исключение. Я считаю, что «официальное» исключение, которое вы должны выбросить, - UnsupportedOperationException. Чтобы быть «правильным», я думаю, что интерфейс Animal должен иметь метод public void climbToATree();. Методы climbToATree() в классах Dog и Hippo должны выдавать UnsupportedOperationException, поскольку они не могут реализовать этот метод. Но если вы вызываете это исключение очень часто, то, возможно, что-то не так с вашей объектной моделью, поскольку я не думаю, что это не обычное дело.

Также обратите внимание, что полезно (но не обязательно) использовать аннотацию @Override с полиморфным программированием на Java. Это вызовет ошибку компиляции, если метод с этой аннотацией не переопределяет родительский метод, не реализует абстрактный метод или (в Java 6) реализует метод интерфейса. Это может помочь отследить любые ошибки, которые вы делаете в сигнатуре метода. Например:

public String tostring(){
  return "foobar";
}

Без аннотации программа успешно скомпилируется и запустится. Но это не было вашим намерением! Вы хотели переопределить toString (), но вы случайно написали неверное имя !!

0 голосов
/ 04 июня 2011

А как насчет кода ниже?Он решает проблему общности, отделяя лазание по деревьям как еще один интерфейс, который вы можете внедрить или нет на своих животных.Это лучше подходит для задачи: лазить по деревьям не свойственно всем животным, только их подмножеству.По крайней мере, для меня это выглядит намного яснее и элегантнее, чем метание NotImplementedException с.


public interface Animal {
    public void talk();
}

public interface AnimalCanClimbTrees extends Animal {
    public void climbToATree();
}

public class Dog implements Animal {
    public void talk() {
        System.out.println("Woof!");
    }
}
/* Animal is probably not needed, but being explicit is never bad */
public class Cat implements Animal, AnimalCanClimbTrees 
{
    public void talk() {
        System.out.println("Meow!");
    }    

    public void climbToATree() {
        System.out.println("Hop, the cat just cimbed to the tree");
    }
}

class Hippopotamus implements Animal {
    public void talk() {
        System.out.println("Roar!");
    }    
}

public class Main {

   public static void main(String[] args) {
       //APPROACH 1
       makeItTalk(new Cat());
       makeItTalk(new Dog());
       makeItTalk(new Hippopotamus());

       //APPROACH 2
       makeItClimbToATree(new Cat());
       makeItClimbToATree(new Hippopotamus());
   }

   public static void makeItTalk(Animal animal) {
       animal.talk();
   }

   public static void makeItClimbToATree(Animal animal) {
       if(animal instanceof AnimalCanClimbTrees) {
           ((AnimalCanClimbTrees)animal).climbToATree();                  
       }
       else {
           System.err.println("That animal cannot climb to a tree");
       }
   }
}
0 голосов
/ 04 июня 2011

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

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

Что касается вопроса о том, когда использовать instanceof, то место, где он почти всегда будет использоваться, - это если переопределить метод equals()класс, так как он принимает только Object, и вы, как правило, должны убедиться, что он того же типа, чтобы его можно было приводить и затем сравнивать.

0 голосов
/ 04 июня 2011

Полиморфный и ООП подход заключается в размещении метода makeItClimbToATree в интерфейсе Animal:

public interface Animal{    
    public void talk(); 
    public void makeItClimbToATree();  
}

Тогда разработчики Animal предоставят поведение для метода, который для всех, кроме Cat, может вызвать исключение. Это полиморфно, потому что вы оперируете различными реализациями Animal одним способом.

Функция, которая использует оператор instanceOf, считается «плохой» ООП, поскольку для определения поведения метода требуется знание всех типов реализации.

...