русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Особенности


Дата добавления: 2015-06-12; просмотров: 761; Нарушение авторских прав


Сложности с программой Music.java обнаруживаются после ее запуска. Она выводит строку Wind.play(). Именно это и требуется, но не понятно, откуда берется такой результат. Взгляните на метод tune():

public static void tune(Instrument i) {

//...

і.play(Note.MIDDLE_C),

}

Метод получает ссылку на объект Instrument. Как компилятор узнает, что ссылка на Instrument в данном случае указывает на объект Wind, а не на Brass или Stringed? Компилятор и не знает. Чтобы в полной мере разобраться в сути про­исходящего, необходимо рассмотреть понятие связывания (binding).

 

Связывание «метод-вызов»

Присоединение вызова метода к телу метода называется связыванием. Если связывание проводится перед запуском программы (компилятором и компоновщиком, если он есть), оно называется ранним связыванием (early binding). Возможно, ранее вам не приходилось слышать этот термин, потому что в процедурных языках никакого выбора связывания не было. Компиляторы C поддерживают только один тип вызова — раннее связывание.

Неоднозначность предыдущей программы кроется именно в раннем связывании: компилятор не может знать, какой метод нужно вызывать, когда у него есть только ссылка на объект Instrument. Проблема решается благодаря позднему связыванию (late binding), то есть связыванию, проводимому во время выполнения программы, в зависимости от типа объекта. Позднее связывание также называют динамическим (dynamic) или связыванием на стадии выполнения (runtime binding).

В языках, реализующих позднее связывание, должен существовать механизм определения фактического типа объекта во время работы программы, для вызова подходящего метода. Иначе говоря, компилятор не знает тип объекта, но механизм вызова методов определяет его и вызывает соответствующее тело метода. Механизм позднего связывания зависит от конкретного языка, но нетрудно предположить, что для его реализации в объекты должна включаться какая-то дополнительная информация.



Для всех методов Java используется механизм позднего связывания, если только метод не был объявлен как final (приватные методы являются final по умолчанию). Следовательно, вам не придется принимать решений относительно использования позднего связывания — оно осуществляется автоматически. Зачем объявлять метод как final? Как уже было замечено в предыдущей главе, это запрещает переопределение соответствующего метода.

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

Получение нужного результата

Теперь, когда вы знаете, что связывание всех методов в Java осуществляется полиморфно, через позднее связывание, вы можете писать код для базового класса, не сомневаясь в том, что для всех производных классов он также будет работать верно. Другими словами, вы «посылаете сообщение объекту и позволяете ему решить, что следует делать дальше».

Классическим примером полиморфизма в ООП является пример с геометрическими фигурами. Он часто используется благодаря своей наглядности, но, к сожалению, некоторые новички начинают думать, что ООП подразумевает графическое программирование — а это, конечно же, неверно.

В примере с фигурами имеется базовый класс с именем Shape (фигура) и различные производные типы: Circle (окружность), Square (прямоугольник), Triangle (треугольник) и т. п. Выражения типа «окружность есть фигура» очевидны и не представляют трудностей для понимания. Взаимосвязи показаны на следующей диаграмме наследования:

Файл:P0203.png


Восходящее преобразование имеет место даже в такой простой команде:

Shape s = new Circle();

Здесь создается объект Circle, и полученная ссылка немедленно присваивается типу Shape. На первый взгляд это может показаться ошибкой (присвоение одного типа другому), но в действительности все правильно, потому что тип Circle (окружность) является типом Shape(фигура) посредством наследования. Компилятор принимает команду и не выдает сообщения об ошибке.

Предположим, вызывается один из методов базового класса (из тех, что были переопределены в производных классах):

s.draw();

Опять можно подумать, что вызывается метод draw() из класса Shape, раз имеется ссылка на объект Shape — как компилятор может сделать что-то другое? И все же будет вызван правильный метод Circle.draw(), так как в программе используется позднее связывание (полиморфизм). Следующий пример показывает несколько другой подход:

//: polymorphism/shape/Shape.java

package polymorphism.shape;

public class Shape {

public void draw() {}

public void erase() {}

}

 

//: polymorphism/shape/Circle.java

package polymorphism.shape;

import static net.mindview.util.Print.*;

public class Circle extends Shape {

public void draw() { print("Circle.draw()"); }

public void erase() { print("Circle.erase()"); }

}

 

//: polymorphism/shape/Triangle.java

package polymorphism.shape;

import static net.mindview.util.Print.*;

public class Triangle extends Shape {

public void draw() { print("Triangle.draw()"); }

public void erase() { print("Triangle.erase()"); }

}

 

//: polymorphism/shape/Square.java

package polymorphism.shape;

import static net.mindview.util.Print.*;

public class Square extends Shape {

public void draw() { print("Square.draw()"); }

public void erase() { print("Square.erase()"); }

}

 

//: polymorphism/shape/RandomShapeGenerator.java

// "Фабрика" случайных фигур.

package polymorphism.shape;

import java.util.*;

public class RandomShapeGenerator {

private Random rand = new Random(47);

public Shape next() {

switch(rand.nextInt(3)) {

default:

case 0: return new Circle();

case 1: return new Square();

case 2: return new Triangle();

}

}

}

 

 

//: polymorphism/Shapes.java

// Polymorphism in Java.

import polymorphism.shape.*;

 

public class Shapes {

private static RandomShapeGenerator gen =

new RandomShapeGenerator();

public static void main(String[] args) {

Shape[] s = new Shape[9];

// Fill up the array with shapes:

for(int i = 0; i < s.length; i++)

s[i] = gen.next();

// Make polymorphic method calls:

for(Shape shp : s)

shp.draw();

}

}

<spoiler text="Output:">

Triangle.draw()

Triangle.draw()

Square.draw()

Triangle.draw()

Square.draw()

Triangle.draw()

Square.draw()

Triangle.draw()

Circle.draw()

</spoiler> Базовый класс Shape устанавливает общий интерфейс для всех классов, производных от Shape — то есть любую фигуру можно нарисовать (draw()) и стереть (erase()). Производные классы переопределяют этот интерфейс, чтобы реализовать уникальное поведение для каждой конкретной фигуры.

Класс RandomShapeGenerator — своего рода «фабрика», при каждом вызове метода next() производящая ссылку на случайно выбираемый объект Shape. Заметьте, что восходящее преобразование выполняется в командах return, каждая из которых получает ссылку на объект Circle, Square или Triangle, а выдает ее за пределы next() в виде возвращаемого типа Shape. Таким образом, при вызове этого метода вы не сможете определить конкретный тип объекта, поскольку всегда получаете просто Shape.

Метод main() содержит массив ссылок на Shape, который заполняется последовательными вызовами RandomShapeGenerator.next(). К этому моменту вам известно, что имеются объекты Shape, но вы не знаете об этих объектах ничего конкретного (так же, как и компилятор). Но если перебрать содержимое массива и вызвать draw() для каждого его элемента, то, как по волшебству, произойдет верное, свойственное для определенного типа действие — в этом нетрудно убедиться, взглянув на результат работы программы.

Случайный выбор фигур в нашем примере всего лишь помогает понять, что компилятор во время компиляции кода не располагает информацией о том, какую реализацию следует вызывать. Все вызовы метода draw() проводятся с при­менением позднего связывания.

Расширяемость

Теперь вернемся к программе Music.java. Благодаря полиморфизму вы можете добавить в нее сколько угодно новых типов, не изменяя метод tune(). В хорошо спланированной ООП-программе большая часть ваших методов (или даже все методы) следуют модели методаtune(), оперируя только с интерфейсом базового класса.

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

Давайте возьмем пример с объектами Instrument и включим дополнительные методы в базовый класс, а также определим несколько новых классов. Рассмотрим диаграмму.

Файл:P0206.png

Все новые классы правильно работают со старым, неизмененным методом tune(). Даже если метод tune() находится в другом файле, а к классу Instrument присоединяются новые методы, он все равно будет работать верно без повторной компиляции. Ниже приведена реализация рассмотренной диаграммы:

//: polymorphism/music3/Music3.java

// Расширяемая программа

package polymorphism.music3;

import polymorphism.music.Note;

import static net.mindview.util.Print.*;

 

class Instrument {

void play(Note n) { print("Instrument.play() " + n); }

String what() { return "Instrument"; }

void adjust() { print("Adjusting Instrument"); }

}

 

class Wind extends Instrument {

void play(Note n) { print("Wind.play() " + n); }

String what() { return "Wind"; }

void adjust() { print("Adjusting Wind"); }

}

 

class Percussion extends Instrument {

void play(Note n) { print("Percussion.play() " + n); }

String what() { return "Percussion"; }

void adjust() { print("Adjusting Percussion"); }

}

 

class Stringed extends Instrument {

void play(Note n) { print("Stringed.play() " + n); }

String what() { return "Stringed"; }

void adjust() { print("Adjusting Stringed"); }

}

 

class Brass extends Wind {

void play(Note n) { print("Brass.play() " + n); }

void adjust() { print("Adjusting Brass"); }

}

 

class Woodwind extends Wind {

void play(Note n) { print("Woodwind.play() " + n); }

String what() { return "Woodwind"; }

}

 

public class Music3 {

// Работа метода не зависит от фактического типа объекта,

// поэтому типы, добавленные в систему, будут работать правильно:

public static void tune(Instrument i) {

// ...

i.play(Note.MIDDLE_C);

}

public static void tuneAll(Instrument[] e) {

for(Instrument i : e)

tune(i);

}

public static void main(String[] args) {

// Upcasting during addition to the array:

Instrument[] orchestra = {

new Wind(),

new Percussion(),

new Stringed(),

new Brass(),

new Woodwind()

};

tuneAll(orchestra);

}

}

<spoiler text="Output:">

Wind.play() MIDDLE_C

Percussion.play() MIDDLE_C

Stringed.play() MIDDLE_C

Brass.play() MIDDLE_C

Woodwind.play() MIDDLE_C

</spoiler> Новый метод what() возвращает строку (String) с информацией о классе, а метод adjust() предназначен для настройки инструментов.

В методе main() сохранение любого объекта в массиве orchestra автоматически приводит к выполнению восходящего преобразования к типу Instrument.

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

Проблема: «переопределение» закрытых методов

Перед вами одна из ошибок, совершаемых по наивности:

//: polymorphism/PrivateOverride.java

// Попытка переопределения приватного метода

package polymorphism;

import static net.mindview.util.Print.*;

 

public class PrivateOverride {

private void f() { print("private f()"); }

public static void main(String[] args) {

PrivateOverride po = new Derived();

po.f();

}

}

 

class Derived extends PrivateOverride {

public void f() { print("public f()"); }

}

<spoiler text="Output:">

private f()

</spoiler> Вполне естественно было бы ожидать, что программа выведет сообщение public f(), но закрытый (private) метод автоматически является неизменным (final), а заодно и скрытым от производного класса. Так что метод f() класса Derived в нашем случае является полностью новым — он даже не был перегружен, так как метод f() базового класса классу Derived недоступен.

Из этого можно сделать вывод, что переопределяются только методы, не являющиеся закрытыми. Будьте внимательны: компилятор в подобных ситуациях не выдает сообщений об ошибке, но и не делает того, что вы от него ожидаете. Иными словами, методам производного класса следует присваивать имена, отличные от имен закрытых методов базового класса.

 

Конструкторы и полиморфизм

Конструкторы отличаются от обычных методов, и эти отличия проявляются и при использовании полиморфизма. Хотя конструкторы сами по себе не полиморфны (фактически они представляют собой статические методы, только ключевое слово static опущено), вы должны хорошо понимать, как работают конструкторы в сложных полиморфных иерархиях. Такое понимание в дальнейшем поможет избежать некоторых затруднительных ситуаций.



<== предыдущая лекция | следующая лекция ==>
Глава 8 ПОЛИМОРФИЗМ | Порядок вызова конструкторов


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.062 сек.