Полиморфизм

Полиморфизм на первый взгляд кажется самой малоинтересной и малоперспективной парадигмой, но на самом деле это совсем не так. Полиморфизм удивительно мощная и востребованная парадигма. Давайте попробуем разобраться, что это такое.
Полиморфизмом назвается возможность работать с несколькими типами так, как будто это один и тот же тип и в то же время поведение каждого типа будет уникальным в зависимости от его реализации. Возможно, что вы ничего не поняли, поэтому попробую описать это иначе и на примере. Давайте так и сделаем.
В предыдущей части мы создавали класс RobotTotal, который наследовался от класса Robot. Если немного подумать, то по парадигме наследования будет интуитивно понятно, что класс RobotTotal является пусть и несколько измененным, но тем не менее классом Robot. Исходя из этого вполне непротиворечивого соображения мы можем написать несколько иную реализацию класса RobotManager

Как видим, создавая объект RobotTotal мы его «сужаем» до объекта класса Robot. С одной стороны это выглядит достаточно логично и непротиворечиво — RobotTotal является роботом. С другой стороны возникает вопрос — а метод forward будет вызываться какой ? Класса RobotTotal или Robot ? Думаю, что для вас ответ «Как RobotTotal» выглядит предпочтительнее — и это соврешенно правильный ответ. Можете в этом убедиться сами, добавив в методв forward какую-либо поясняющую печать. Например так:

В этом можно убедиться еще более удобным и практичным образом — спросить у объекта его класс и у класса спросить его имя. Этот механизм называется Reflection — он позволяет получить информацию об объекте прямо в момент выполнения программы. Мы будем его рассматривать несколько позже. Вот как может выглядеть такой вызов:

System.out.println(robot.getClass().getName());

Расшифровывается это приблизительно так: сначала у объекта robot получаем его класс методом getClass(). Возвращается объект типа Class (есть такой стандартный класс — Class). И у этого класса есть метод, который возращает имя — getName(). Вот возможный полный код:

Как видим мы можем присвоить ссылке на объект класса-предка объект-потомок — и это работает. С одной стороны, мы работаем как-будто с классом Robot, с другой стороны — поведение нашего обхекта соответствует классу RobotTotal. ВАЖНО !!! А вот в обратную сторону присваивание НЕ работает. На запись вот такого вида

RobotTotal robot = new Robot(0, 0);

компилятор будет выдавать ошибку.
Думаю, что с технической точки зрения все достаточно понятно. Но возникает логичный вопрос — зачем это вообще надо ? С инкапсуляцией более-менее понятно, с наследованием — в принципе тоже. Но вот этот механизм зачем, какое преимущество мы получим при использовании этой парадигмы ? Для первого приближения рассмотрим наш пример графического приложения, в котором мы создавали свой компонент OvalComponent. Мы использовали модифицированный класс JFrame (OvalFrame) и что весьма важно, мы использовали уже готовый метод add для добавления нашего объекта на форму. Я бы хотел заострить ваше внимание на этом весьма тонком моменте — мы использовали УЖЕ существующий метод существующего класса JFrame. И этот метод (да и класс тоже) совершенно не осведомлен о нашем новом классе OvalComponent. И тем не менее он прекрасно с ним работает — мы сами это видели. Думаю, что вы уже догадались в чем фокус, но я тем не менее проговорю эту мысль — класс JFrame умеет работать с классами-потомками от класса JComponent и ему не важно, какой точно класс он получил — они для него все являются объектами класса JComponent. И это здорово нам помогает. Т.к. наш класс OvalComponent рисует себя сам путем вызова метода paintComponent где-то у себя, то ситуация еще более восхитительна — вызывается именно НАШ метод paintComponent. Значит мы можем написать много разных классов унаследованных от класса JComponent, положить их на форму и все они будут рисоваться так, как они сами это умеют.
Что еще интересно — для самого себя класс тоже может вести себя полиморфно. Если внимательно посмотреть на код класса OvalComponent метод paintComponent объявлен как protected и вызывается внутри класса JComponent и никак иначе. Снаружи другим классам он недоступен. Т.е. все наследники класса JComponent предоставляют свои реализации метода paintComponent и вызывают его внутри унаследованного метода paint, который уже объявлен как public. Иными словами — все наследники используют метод paintComponent из уже готового метода paint. Возможно, вы еще не совсем готовы оценить «красоту игры», но на мой взгляд полиморфизм является просто чудесной штукой. Мы еще вернемся к этой весьма увлекательной парадигме, ну а пока сделаем еще одно графическое приложение, которое позволит нам поместить разные типы компонентов на форму, о которых она не знает, но тем не менее сможет прекрасно ими управлять.

Графическое приложение

Данное приложение несколько сложнее предыдущего — здесь создаются разные компоненты для рисования разных фигур. Приложение содержит 5 классов: 3 класса являются компонентами, которые рисуют внутри себя три разных фигуры (овал, треугольник и прямоугольник), класс для отображения окна и класс для создания и отображения самой формы. Сначала рассмотрим три класса для рисования фигур. Они используют методы класса Graphics и вряд ли требуют каких-либо комментариев. Я их поместил в отдельный пакет.

В классе формы мы используем механизм для установки LayoutManager. Если в двух словах, то идея заключается в следующем — форме можно указать алгоритм (правила) размещения компонентов. Реализации этого алгоритма выносится в отдельный класс (который имеет обобщенное название LayoutManager) и задается форме (более правильно будет сказать, что задается для контейнера). При рисовании форма использует это класс для определения, как размещать компоненты. Подробнее можно почитать в статье Что такое LayoutManager.

Еще раз обратите внимание, что когда мы вызываем метод add мы передаем форме разные объекты. Но что здесь важно — они все наследники JComponent. Форме в принципе надо получить еще более «старого» предка — java.awt.Component. Можно конечно углубиться в исходники, но давайте пока немного упростим и поймем главное — форма «думает», что она «работает» в классом JComponent. Именно у этого класса она вызывает метод прорисовки paintComponent (повторюсь — это не совсем так, но сейчас мы упрощаем для понимания главной идеи полиморфизма). Мы в какой-то мере «обманываем» форму — подсовываем ей компонент, который за счет наследования имеет модифицированный метод прорисовки и когда форма «полагает», что она вызывает метод paintComponent у объекта класса JComponent на самом деле она вызывает метод paintComponent у объекта уже нашего нового класса OvalComponent или RectangleComponent.
Полиморфизм таким образом позволяет вам подменять объекты для тех кто их вызывает и вызывающий даже не знает об этом. Оглушительная возможность.
Заключительный класс для запуска формы мы уже видели. Наверно уже есть смысл отметить, что с точки зрения правильного создания и запуска формы наше приложение не совсем корректно. Но для упрощения мы пока идем на такой шаг — наша форма достаточно простая и может быть создана так, как показано.

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

Мы рассмотрели основные парадигмы ООП и теперь перед нами задача более подробно познакомиться с конструкциями и идеями языка Java. Чем мы с вами и займемся.

И теперь нас ждет следующая статья: Статические свойства и методы