На самом деле, вы сказали это довольно хорошо прямо там.
Правда в том, что «экземпляр» гребня почти всегда является плохой идеей (исключение происходит, например, когда вы выполняете маршалинг или сериализацию, когда в течение короткого интервала у вас может не быть всей информации о типе под рукой). Как говорит Джош, в противном случае это признак плохой иерархии классов.
То, как вы знаете , плохая идея в том, что это делает код хрупким: если вы его используете, а иерархия типов меняет , то, вероятно, нарушает этот экземпляр. расчески везде, где это происходит. Более того, вы теряете преимущество сильной печати; компилятор не может помочь вам, отлавливая ошибки заранее. (Это несколько похоже на проблемы, вызванные типами в C).
Обновление
Позвольте мне немного расширить это, поскольку, судя по комментарию, я не совсем понял. Причина, по которой вы используете типизацию в C или instanceof
, заключается в том, что вы хотите сказать «как будто»: используйте этот foo
, как если бы это был bar
. Теперь в C вообще нет никакой информации о типе времени выполнения, так что вы просто работаете без сети: если вы что-то типизируете, сгенерированный код будет обрабатывать этот адрес, как если бы он содержал определенный тип, независимо от того, что и вам следует только надеяться на то, что это вызовет ошибку во время выполнения вместо тихого повреждения чего-либо.
Утиное печатание просто поднимает это до нормы; в динамическом, слабо типизированном языке, таком как Ruby, Python или Smalltalk, все является нетипизированной ссылкой; Вы стреляете в сообщения во время выполнения и смотрите, что происходит. Если он понимает конкретное сообщение, он «ходит как утка» - он обрабатывает его.
Это может быть очень удобно и полезно, потому что позволяет совершать чудесные хаки, такие как присвоение выражения генератора переменной в Python или блока переменной в Smalltalk. Но это означает, что вы уязвимы к ошибкам во время выполнения, которые строго типизированный язык может обнаружить во время компиляции.
В строго типизированном языке, таком как Java, на самом деле вы вообще не можете иметь утиную типизацию: вы должны сообщить компилятору, к какому типу вы собираетесь относиться к чему-либо. Вы можете получить что-то похожее на утку, используя приведение типов, так что вы можете сделать что-то вроде
Object x; // A reference to an Object, analogous to a void * in C
// Some code that assigns something to x
((FoodDispenser)x).dropPellet(); // [1]
// Some more code
((MissleController)x).launchAt("Moon"); // [2]
Теперь во время выполнения вы в порядке, если x является своего рода FoodDispenser
в [1] или MissleController
в [2]; в противном случае бум . Или неожиданно нет бум .
В своем описании вы защищаете себя с помощью расчески else if
и instanceof
Object x ;
// code code code
if(x instanceof FoodDispenser)
((FoodDispenser)x).dropPellet();
else if (x instanceof MissleController )
((MissleController)x).launchAt("Moon");
else if ( /* something else...*/ ) // ...
else // error
Теперь вы защищены от ошибок во время выполнения, но вы несете ответственность за то, чтобы сделать что-то разумное позже, в else
.
Но теперь представьте, что вы вносите изменения в код, чтобы «x» мог принимать типы «FloorWax» и «DessertTopping». Теперь вы должны пройти через весь код и найти все экземпляры этого гребня и изменить их. Теперь код «хрупкий» - изменения в требованиях означают множество изменений кода. В ОО вы стремитесь сделать код менее хрупким.
Решение OO состоит в том, чтобы использовать вместо этого полиморфизм, который вы можете рассматривать как некий ограниченный тип утки: вы определяете все операции, которым можно доверять. Вы делаете это путем определения высшего класса, возможно, абстрактного, который имеет все методы низших классов. В Java такой класс лучше всего выражать как «интерфейс», но он имеет все свойства типа класса. Фактически, вы можете видеть интерфейс как обещание, что определенному классу можно доверять, чтобы он действовал «как если бы» был другим классом.
public interface VeebleFeetzer { /* ... */ };
public class FoodDispenser implements VeebleFeetzer { /* ... */ }
public class MissleController implements VeebleFeetzer { /* ... */ }
public class FloorWax implements VeebleFeetzer { /* ... */ }
public class DessertTopping implements VeebleFeetzer { /* ... */ }
Все, что вам нужно сделать сейчас, это использовать ссылку на VeebleFeetzer, и компилятор выяснит это для вас. Если вам случится добавить другой класс, который является подтипом VeebleFeetzer, компилятор выберет метод и проверит аргументы в сделке
VeebleFeetzer x; // A reference to anything
// that implements VeebleFeetzer
// Some code that assigns something to x
x.dropPellet();
// Some more code
x.launchAt("Moon");