Как сделать полиморфизм

Обновлено: 07.07.2024

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

Например, два разных класса содержат метод total, однако инструкции каждого предусматривают совершенно разные операции. Так в классе T1 – это прибавление 10 к аргументу, в T2 – подсчет длины строки символов. В зависимости от того, к объекту какого класса применяется метод total, выполняются те или иные инструкции.

В предыдущем уроке мы уже наблюдали полиморфизм между классами, связанными наследованием. У каждого может быть свой метод __init__() или square() или какой-нибудь другой. Какой именно из методов square() вызывается, и что он делает, зависит от принадлежности объекта к тому или иному классу.

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

Полиморфизм дает возможность реализовывать так называемые единые интерфейсы для объектов различных классов. Например, разные классы могут предусматривать различный способ вывода той или иной информации объектов. Однако одинаковое название метода вывода позволит не запутать программу, сделать код более ясным.

В Python среди прочего полиморфизм находит отражение в методах перегрузки операторов. Два из них мы уже рассмотрели. Это __init__() и __del__(), которые вызываются при создании объекта и его удалении. Полиморфизм у методов перегрузки операторов проявляется в том, что независимо от типа объекта, его участие в определенной операции, вызывает метод с конкретным именем. В случае __init__() операцией является создание объекта.

Рассмотрим пример полиморфизма на еще одном методе, который перегружает функцию str(), которую автоматически вызывает функция print().

Если вы создадите объект собственного класса, а потом попробуете вывести его на экран, то получите информацию о классе объекта и его адрес в памяти. Такое поведение функции str() по-умолчанию по отношению к пользовательским классам запрограммировано на самом верхнем уровне иерархии, где-то в суперклассе, от которого неявно наследуются все остальные.

Здесь мы используем переменную b , чтобы показать, что функция print() вызывает str() неявным образом, так как вывод значений обоих переменных одинаков.

Если же мы хотим, чтобы, когда объект передается функции print(), выводилась какая-нибудь другая более полезная информация, то в класс надо добавить специальный метод __str__(). Этот метод должен обязательно возвращать строку, которую будет в свою очередь возвращать функция str(), вызываемая функций print():

Какую именно строку возвращает метод __str__(), дело десятое. Он вполне может строить квадратик из символов:

Практическая работа. Метод перегрузки оператора сложения

В качестве практической работы попробуйте самостоятельно перегрузить оператор сложения. Для его перегрузки используется метод __add__(). Он вызывается, когда объекты класса, имеющего данный метод, фигурируют в операции сложения, причем с левой стороны. Это значит, что в выражении a + b у объекта a должен быть метод __add__(). Объект b может быть чем угодно, но чаще всего он бывает объектом того же класса. Объект b будет автоматически передаваться в метод __add__() в качестве второго аргумента (первый – self).

Отметим, в Python также есть правосторонний метод перегрузки сложения - __radd__().

Согласно полиморфизму ООП, возвращать метод __add__() может что угодно. Может вообще ничего не возвращать, а "молча" вносить изменения в какие-то уже существующие объекты. Допустим, в вашей программе метод перегрузки сложения будет возвращать новый объект того же класса.

Курс с примерами решений практических работ:
android-приложение, pdf-версия

Когда программисты говорят о C++ и объектно-ориентированном программировании, то очень часто употребляют термин полиморфизм. В общем случае полиморфизм представляет собой способность объекта изменять форму. Если вы разделите этот термин на части, то обнаружите, что поли означает много, а морфизм относится к изменению формы. Полиморфный объект, следовательно, представляет собой объект, который может принимать разные формы. Этот урок вводит понятие полиморфизма и рассматривает, как использовать полиморфные объекты внутри ваших программ для упрощения и уменьшения кода. К концу данного урока вы освоите следующие основные концепции:

  • Полиморфизм представляет собой способность объекта изменять форму во время выполнения программы.
  • C++ упрощает создание полиморфных объектов.
  • Для создания полиморфных объектов ваши программы должны использовать виртуальные (virtual) функции.
  • Виртуальная (virtual) функция — это функция базового класса, перед именем которой стоит ключевое слово virtual.
  • Любой производный от базового класс может использовать или перегружать виртуальные функции.
  • Для создания полиморфного объекта вам следует использовать указатель на объект базового класса.

ЧТО ТАКОЕ ПОЛИМОРФИЗМ

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

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

СОЗДАНИЕ ПОЛИМОРФНОГО ОБЪЕКТА-ТЕЛЕФОНА

После того как вы показали начальству вашу новую телефонную программу, ваше руководство сказало вам, что ваш объект-телефон должен уметь эмулировать дисковый, кнопочный или платный телефон на выбор. Другими словами, для одного звонка объект-телефон мог бы представлять кнопочный аппарат, для другого выступал бы как платный телефон и т.д. Иначе говоря, от одного звонка к другому ваш объект-телефон должен изменять форму.

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

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

Поскольку объект poly_phone изменяет форму по мере выполнения программы, он является полиморфным.

Полиморфные объекты могут изменять форму во время выполнения программы

Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Для создания полиморфного объекта ваша программа должна использовать указатель на объект базового класса. Далее программа должна присвоить этому указателю адрес объекта производного класса. Каждый раз, когда программа присваивает указателю адрес объекта другого класса, объект этого указателя (который является полиморфным) изменяет форму. Программы строят полиморфные объекты, основываясь на виртуальных функциях базового класса.

ЧТО ТАКОЕ ЧИСТО ВИРТУАЛЬНЫЕ ФУНКЦИИ

Как вы уже знаете, для создания полиморфного объекта ваши программы определяют один или несколько методов базового класса как виртуальныефункции. Производный класс может определить свою собственную функцию, которая выполняется вместо виртуальной функции базового класса, или использовать базовую функцию (другими словами, производный класс может и не определять свой собственный метод). В зависимости от программы иногда не имеет смысла определять виртуальную функцию в базовом классе. Например, объекты производных типов могут настолько сильно отличаться, что им не нужно будет использовать метод базового класса. В таких случаях вместо определения операторов для виртуальной функции базового класса ваши программы могут создать чисто виртуальную функцию, которая не содержит операторов.

Для создания чисто виртуальной функции ваша программа указывает прототип функции, но не указывает ее операторы. Вместо них программа присваивает функции значение ноль, как показано ниже:

class phone

public:
virtual void dial (char *number) =0; // Чисто виртуальнаяфункция
void answer(void) < cout

Позднее и раннее связывание. Полиморфизм. Основные понятия. Примеры. Передача в метод ссылки на базовый класс. Ключевые слова virtual , override , new

Данная тема есть продолжением темы:

Содержание

Поиск на других ресурсах:

1. Понятие позднего и раннего связывания. Ключевые слова virtual , override

При изучении темы полиморфизма важно понять понятие позднего и раннего связывания, которое используется компилятором при построении кода программы в случае наследования.

Если классы образовывают иерархию наследования, то при обращении к элементам класса, компилятор может реализовывать один из двух возможных способов связывания кода:

  1. Раннее связывание – связанное с формированием кода на этапе компиляции. При раннем связывании, программный код формируется на основе известной информации о типе (класс) ссылки. Как правило, это ссылка на базовый класс в иерархии классов.
  2. Позднее связывание – связанное с формированием кода на этапе выполнения. Если в иерархии классов встречается цепочка виртуальных методов (с помощью слов virtual, override), то компилятор строит так называемое позднее связывание. При позднем связывании вызов метода происходит на основании типа объекта, а не типа ссылки на базовый класс. Позднее связывание используется, если нужно реализовать полиморфизм.

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

Необходимые условия для реализации позднего связывания:

  • классы должны образовывать иерархию наследования;
  • в классах должны быть методы с одинаковой сигнатурой. Элементы (методы) производных классов должны перекрывать (override) соответствующие элементы (методы) базовых классов;
  • элементы (методы) класса должны быть виртуальными, то есть должны быть обозначены ключевыми словами virtual, override.

На рисунке 1 приведен пример, который отображает отличие между поздним и ранним связыванием на примере двух классов A , B в которых реализован метод Print() .

Рисунок 1. Позднее и раннее связывание. Отличия

В случае раннего связывания, как только компилятор встречает строку

происходит объявление ссылки ref , которая имеет тип базового класса A . Дальнейшее присваивание

связывает ссылку с объектом objB , однако тип ссылки устанавливается A . Поэтому вызов

вызовет метод Print() класса A .

В случае позднего связывания, сначала на основе описания классов A , B компилятор определяет, что метод Print() есть виртуальным. Для виртуального метода компилятор строит таблицу виртуальных методов Print() , которая содержит смещение адресов каждого виртуального метода для каждого класса иерархии (это отдельная тема для исследования).

формируется связывание ссылки ref с типом A . После присваивания

Как следствие, после вызова

будет вызван метод Print() класса B.

На рисунке 2 приведено отличие между поздним и ранним связываниями на примере трех классов A, B , C , в каждом из которых объявлен метод Print() .

Рисунок 2. Раннее и позднее связывание для метода Print() на примере трех классов A , B , C

Вызов метода Print() по ссылке на объект класса C

2. Что такое полиморфизм? Динамический полиморфизм

Полиморфизм – это свойство программного кода изменяться в зависимости от ситуации, которая возникает в момент выполнения программы.

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

Использование преимуществ полиморфизма возможно в ситуациях:

3. Для каких элементов класса можно применять полиморфизм?

Полиморфизм можно применять для следующих элементов:

  • методов;
  • свойств;
  • индексаторов;
  • событий.
4. Схематическое объяснение полиморфизма

На рисунке 3 демонстрируется применение полиморфизма на примере двух классов.

Рисунок 3. Реализация полиморфизма на примере двух классов A , B

5. Полиморфизм в случае передачи в метод ссылки на базовый класс. Позднее связывание

В любой метод может быть передана ссылка на базовый класс. С помощью этой ссылки также можно вызвать методы, свойства которые поддерживают полиморфизм.

Пример.

Заданы 2 класса с именами Base и Derived . Класс Derived наследует класс Base . В обоих классах определен виртуальный метод Info() , который выводит информацию о классе. Вызов виртуального метода Info() осуществляется из статического метода ShowInfo() класса Program . Метод ShowInfo() получает входным параметром ссылку на базовый класс и по этой ссылке обращается к методу Info() класса. В зависимости от того, экземпляр какого класса передается в метод ShowInfo() , соответствующий метод Info() вызывается.

6. Какие требования накладываются на элемент класса для того, чтобы он поддерживал полиморфизм?

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

  • в базовом классе этот элемент (метод, свойство) должен быть обозначен как virtual или abstract. Ключевое слово abstract также делает элемент виртуальным. Это слово используется, если элемент класса есть абстрактным. Более подробно об абстрактных классах описывается здесь ;
  • в производных классах одноименные элементы должны быть обозначены как override . Если в производном классе нужно реализовать невиртуальный метод, имя которого совпадает с виртуальным методом базового класса, то этот метод обозначается ключевым словом new (смотрите пункт 7).
7. Использование ключевого слова new в цепочке виртуальных методов. Пример

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

Для того, чтобы элемент класса, который переопределяет ( override ) виртуальный элемент базового класса, не поддерживал полиморфизм нужно указать ключевое слово new . Если в цепочке одноименных виртуальных методов встречается один невиртуальный метод (с ключевым словом new ) то этот метод разрывает цепочку.

Пример. Заданы классы с именами A1 , A2 , A3 , A4 . Классы образовывают иерархию наследования. В классе A3 объявляется метод Print() с ключевым словом new , разрывающий цепочку виртуальных методов.

Текст демонстрационной программы следующий

На рисунке 4 схематично изображен вызов метода Print() в случае использования ключевого слова new.

Рисунок 4. Иерархия классов A1 , A2 , A3 , A4 . Разрыв цепи виртуальных методов Print() в классе A3

Результат работы программы

8. Пример классов Figure -> Rectangle -> RectangleColor , демонстрирующих полиморфизм

Объявить класс Figure , содержащий поле name , которое определяет название фигуры. В классе Figure объявить следующие методы:

  • конструктор с 1 параметром;
  • метод Display() , отображающий название фигуры.

Из класса Figure унаследовать класс Rectangle (прямоугольник), который содержит следующие поля:

  • координату левого верхнего угла (x1; y1);
  • координату правого нижнего угла (x2; y2).

В классе Rectangle реализовать следующие методы и функции:

  • конструктор с 5 параметрами, который вызывает конструктор базового класса Figure ;
  • конструктор без параметров, который реализует установку координат углов (0; 0), (1; 1) и вызывает конструктор с 5 параметрами с помощью средства this ;
  • метод Display() , отображающий название фигуры и значения внутренних полей. Данный метод обращается к одноименному методу базового класса;
  • метод Area() , возвращающий площадь прямоугольника.

Из класса Rectangle унаследовать класс RectangleColor . В классе RectangleColor реализовать поле color (цвет) и следующие методы;

Рассмотрим ещё одного из китов объектно-ориентированного программирования – полиморфизм. Что это такое, и как его применять на практике.

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

В двух словах полиморфизм – это различная реализация однотипных действий через единый интерфейс, при условии, что данный интерфейс наследуется всеми видами реализации.

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

Привет! Это статья об одном из принципов ООП - полиморфизм.

Определение полиморфизма звучит устрашающе 🙂

Полиморфизм - это возможность применения одноименных методов с одинаковыми или различными наборами параметров в одном классе или в группе классов, связанных отношением наследования.

Слово "полиморфизм" может показаться сложным - но это не так. Нужно просто разбить данное определение на части и показать на примерах, что имеется в виду. Поверьте, уже в конце статьи данное определение полиморфизма покажется Вам понятным 🙂

Полиморфизм, если перевести, - это значит "много форм". Например, актер в театре может примерять на себя много ролей - или принимать "много форм".


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

Так о каких формах идет речь? Давайте сначала приведем примеры и покажем, как на практике проявляется полиморфизм, а потом снова вернемся к его определению.

Как проявляется полиморфизм

Дело в том, что если бы в Java не было принципа полиморфизма, компилятор бы интерпретировал это как ошибку:


Как видите, методы на картинке отличаются значениями, которые они принимают:

Однако, поскольку в Java используется принцип полиморфизма, компилятор не будет воспринимать это как ошибку, потому что такие методы будут считаться разными:


Называть методы одинаково - это очень удобно. Например, если у нас есть метод, который ищет корень квадратный из числа, гораздо легче запомнить одно название (например, sqrt()), чем по одному отдельному названию на этот же метод, написанный для каждого типа:


Как видите, мы не должны придумывать отдельное название для каждого метода - а главное их запоминать! Очень удобно.

Теперь Вы можете понять, почему часто этот принцип описывают фразой:

Один интерфейс - много методов

Это предполагает, что мы можем заполнить одно название (один интерфейс), по которому мы сможем обращаться к нескольким методам.

Перегрузка методов

То, что мы показывали выше - несколько методов с одним названием и разными параметрами - называется перегрузкой. Но это был пример перегрузки метода в одном классе. Но бывает еще один случай - переопределение методов родительского класса.

Переопределение методов родителя

Когда мы наследуем какой-либо класс, мы наследуем и все его методы. Но если нам хочется изменить какой-либо из методов, который мы наследуем, мы можем всего-навсего переопределить его. Мы не обязаны, например, создавать отдельный метод с похожим названием для наших нужд, а унаследованный метод будет "мертвым грузом" лежать в нашем классе.

Именно то, что мы можем создать в классе-наследнике класс с таким же названием, как и класс, который мы унаследовали от родителя, и называется переопределением.

Пример

Представим, что у нас есть такая структура:

Вверху иерархии классов стоит класс Animal. Его наследуют три класса - Cat, Dog и Cow.

Поэтому, в классах-наследниках мы переопределяем метод voice(), чтобы мы в консоли получали "Мяу", "Гав" и "Муу".

  • Обратите внимание: перед методом, который мы переопределяем, пишем "@Override". Это дает понять компилятору, что мы хотим переопределить метод.

Так что же такое полиморфизм

Тем не менее, полиморфизм - это принцип. Все реальные примеры, которые мы приведодили выше - это только способы реализации полиморфизма.

Давайте снова посмотрим на определение, которое мы давали в начале статьи:

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

Выглядит понятнее, правда? Мы показали, как можно:

  • создавать "одноименные методы" в одном классе ("перегрузка методов")
  • или изменить поведение методов родительского класса ("переопределение методов").

Все это - проявления "повышенной гибкости" объектно-ориентированных языков благодаря полиморфизму.

Надеемся, наша статья была Вам полезна. Записаться на наши курсы по Java можно у нас на сайте.

Читайте также: