Ответ объясняется главной особенностью объектно-ориентированного программирования: компилятор не может вызывать такие функции традиционным способом. При вызовах функций, созданных не ООП-компилятором, используется
Для решения этой задачи языки объектно-ориентированного программирования используют концепцию
Для осуществления позднего связывания Java вместо абсолютного вызова использует специальные фрагменты кода. Этот код вычисляет адрес тела метода на основе информации, хранящейся в объекте (процесс очень подробно описан в главе 7). Таким образом, каждый объект может вести себя различно, в зависимости от содержимого этого кода. Когда вы посылаете сообщение, объект фактически сам решает, что же с ним делать.
В некоторых языках необходимо явно указать, что для метода должен использоваться гибкий механизм позднего связывания (в С++ для этого предусмотрено ключевое слово virtual). В этих языках методы по умолчанию компонуются
Вспомним о примере с фигурами. Семейство классов (основанных на одинаковом интерфейсе) было показано на диаграмме чуть раньше в этой главе. Для демонстрации полиморфизма мы напишем фрагмент кода, который игнорирует характерные особенности типов и работает только с базовым классом. Этот код
Допустим, вы написали на Java следующий метод (вскоре вы узнаете, как это делать):
void doSomething(Shape shape) { shape.eraseO: II стереть
shape.drawO, II нарисовать }
Метод работает с обобщенной фигурой (Shape), то есть не зависит от конкретного типа объекта, который рисуется или стирается. Теперь мы используем вызов метода doSomething() в другой части программы:
Circle circle = new CircleO. // окружность Triangle triangle = new TriangleO; II треугольник Line line = new LineO; // линия doSomething(circle). doSomething(triangle). doSomething( line);
Вызовы метода doStuff() автоматически работают правильно, вне зависимости от фактического типа объекта. На самом деле это довольно важный факт. Рассмотрим строку:
doSomething(c);
Здесь происходит следующее: методу, ожидающему объект Shape, передается объект «окружность» (Circle). Так как окружность (Circle) одновременно является фигурой (Shape), то метод doSomething() и обращается с ней, как с фигурой. Другими словами, любое сообщение, которое метод может послать Shape, также принимается и Circle. Это действие совершенно безопасно и настолько же логично.
Мы называем этот процесс обращения с производным типом как с базовым
Объектно-ориентированная программа почти всегда содержит восходящее преобразование, потому что именно так вы избавляетесь от необходимости знать точный тип объекта, с которым работаете. Посмотрите на тело метода doSomething():
shape erase().
// .
shape drawO,
Заметьте, что здесь не сказано «если ты объект Circle, делай это, а если ты объект Square, делай то-то и то-то». Такой код с отдельными действиями для каждого возможного типа Shape будет путаным, и его придется менять каждый раз при добавлении нового подтипа Shape. А так, вы просто говорите: «Ты фигура, и я знаю, что ты способна нарисовать и стереть себя, ну так и делай это, а о деталях позаботься сама».