В главе 6 было показано, что объект можно использовать как своего типа или как экземпляр базового типа. Действие получения ссылки на объект и использование её как ссылки на базовый тип называется "вверх по иерархии" — так как диаграмма наследования обычно располагает базовые классы в верхней части.
Однако это также может привести к проблемам, как показано ниже (если возникают проблемы при выполнении этого примера, обратитесь к разделу Yöntem Atama" главы 3):
//: Music.java
// Наследование и вверх по иерархии
package c07;
class Note {
private int value;
private Note(int val) { value = val; }
public static final Note
middleC = new Note(0),
cSharp = new Note(1),
cFlat = new Note(2);
} // И т.д.
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
// Объекты Wind являются инструментами,
// поскольку они имеют одинаковое API:
class Wind extends Instrument {
// Переопределяем метод интерфейса:
public void play(Note n) {
System.out.println("Wind.play()");
}
}
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.middleC);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Вверх по иерархии
}
} ///:~
Проблемы могут возникнуть, если метод play
переопределен в производном классе и поведение метода будет зависеть от конкретной реализации.Здесь метод Music.tune()
принимает ссылку на Instrument
, а также на все производные от Instrument
. Когда ссылка на Wind
передаётся в tune()
, нет необходимости преобразования. Это допустимо; интерфейс Instrument
должен существовать в Wind
, так как Wind
наследуется от Instrument
. Преобразование снизу вверх от Wind
до Instrument
может "сужать" этот интерфейс, но никогда не делает его меньше полного интерфейса Instrument
.## 7.1.1 Почему нужен вверх по иерархии?
Этот пример может выглядеть странным. Почему всем следует забывать тип объекта? При использовании вверх по иерархии могут возникнуть такие вопросы. Кроме того, если бы tune()
просто получал ссылку на Wind
и использовал её как параметр, это могло бы быть проще и более очевидным. Однако важно отметить: если бы это было сделано, потребовалось бы создание нового метода tune()
для каждого типа Instrument
внутри системы. Предположим, что мы добавили два новых типа Instrument
: Stringed
(строе) и Brass
(медные):
//: Music2.java
// Переопределение вместо приведения типов
class Note2 {
private int value;
private Note2(int val) { value = val; }
public static final Note2
middleC = new Note2( Yöntem2(0)),
cSharp = new Note2( Yöntem2(1)),
cFlat = new Note2( Yöntem2(2));
} // И так далее.
class Instrument2 {
public void play(Note2 n) {
System.out.println("Instrument2.play()");
}
}
class Wind2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Wind2.play()");
}
}
class Stringed2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Stringed2.play()");
}
}
class Brass2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Brass2.play()");
}
}
public class Music2 {
public static void tune(Wind2 i) {
i.play(Note2.middleC);
}
public static void tune(Stringed2 i) {
i.play(Note2.middleC);
}
public static void tune(Brass2 i) {
i.play(Note2.middleC);
}
public static void main(String[] args) {
Wind2 flute = new Wind2();
Stringed2 violin = new Stringed2();
Brass2 frenchHorn = new Brass2();
tune(flute); // Без приведения типов
tune(violin);
tune(frenchHorn);
}
} ///:~
```Конечно, это работает, но существует существенный недостаток: требуется написание методов, связанных с каждым новым классом `Instrument2`. Это означает, что в первый раз потребуется гораздо больше кода. В будущем, если вы хотите добавить новый метод, такой как `tune()`, или новый тип для `Instrument`, вам всё равно придётся писать много кода. Кроме того, даже если вы забудете переопределить какой-либо метод, компилятор не сообщит вам об этом. Таким образом, управление всеми этими типами становится очень сложной задачей, которая может легко выйти из-под контроля.Но что, если бы мы просто написали один метод, используя базовый класс в качестве аргумента, а не специфические производные классы? То есть, если бы мы могли игнорировать производные классы и работать только с базовым классом, количество необходимого кода значительно уменьшилось бы.
Это именно то место, где "полиморфизм" проявляет свою мощь. Однако большинство программистов (особенно те, кто имеет опыт процедурного программирования) всё ещё чувствуют себя несколько неловко при работе с полиморфизмом.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )