Программирование на Java

       

Полиморфизм


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

Предположим, мы хотим создать векторный графический редактор, в котором нам нужно описать в виде классов набор графических примитивов - Point, Line, Circle, Box и т.д. У каждого из этих классов определим метод draw для отображения соответствующего примитива на экране.

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

… //создание пустого массива, который может // содержать объекты Point с максимальным // объемом 1000 Point[] p = new Point[1000];

Line[] l = new Line[1000]; Circle[] c = new Circle[1000]; Box[] b = new Box[1000]; … // предположим, в этом месте происходит // заполнение всех массивов соответствующими // объектами … for(int i = 0; i < p.length;i++) { //цикл с перебором всех ячеек массива. //вызов метода draw() в случае, // если ячейка не пустая. if(p[i]!=null) p.draw(); }

for(int i = 0; i < l.length;i++) { if(l[i]!=null) l.draw(); }

for(int i = 0; i < c.length;i++) { if(c[i]!=null) c.draw(); }

for(int i = 0; i < b.length;i++) { if(b[i]!=null) b.draw(); } …

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

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

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

Для описанной выше иерархии классов, используя полиморфизм, можно написать следующий код:


… Point p[] = new Point[1000]; p[0] = new Circle(); p[1] = new Point(); p[2] = new Box(); p[3] = new Line(); … for(int i = 0; i < p.length;i++) { if(p[i]!=null) p[i].draw(); } …

В описанном выше примере массив p[] может содержать любые объекты, порожденные от наследников класса Point. При вызове какого-либо метода у любого из элементов этого массива будет выполнен метод того объекта, который содержится в ячейке массива. Например, если в ячейке p[0] находится объект Circle, то при вызове метода draw следующим образом:

p[0].draw()

нарисуется круг, а не точка.

В заключение приведем формальное определение полиморфизма.

Полиморфизм (polymorphism) - положение теории типов, согласно которому имена (например, переменных) могут обозначать объекты разных (но имеющих общего родителя) классов. Следовательно, любой объект, обозначаемый полиморфным именем, может по-своему реагировать на некий общий набор операций [2].

В процедурном программировании тоже существует понятие полиморфизма, которое отличается от рассмотренного механизма в ООП. Процедурный полиморфизм предполагает возможность создания нескольких процедур или функций с одним и тем же именем, но разным количеством или различными типами передаваемых параметров. Такие одноименные функции называются перегруженными, а само явление - перегрузкой (overloading). Перегрузка функций существует и в ООП и называется перегрузкой методов.


Рис. 2.3.  Пример иерархии классов.

Примером использования перегрузки методов в языке Java может служить класс PrintWriter, который применяется, в частности, для вывода сообщений на консоль. Этот класс имеет множество методов println, которые различаются типами и/или количеством входных параметров. Вот лишь несколько из них:

void println() // переход на новую строку void println(boolean x) // выводит значение булевской // переменной (true или false) void println(String x) // выводит строку - значение // текстового параметра.

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


Содержание раздела